├── .babelrc ├── .bowerrc ├── .gitattributes ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── dist ├── angular-websocket-mock.js ├── angular-websocket.js ├── angular-websocket.min.js └── angular-websocket.min.js.map ├── example ├── browserify-example │ ├── .jshintrc │ ├── app │ │ ├── app.js │ │ ├── controllers.js │ │ ├── filters.js │ │ └── services.js │ ├── bundle.js │ └── index.html ├── index.html └── scripts.js ├── karma.conf.js ├── package.json ├── src ├── angular-websocket-mock.js └── angular-websocket.js └── test └── angular-websocket.spec.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ], 5 | "plugins": [ 6 | "add-module-exports", 7 | "transform-es2015-modules-umd" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "json": "bower.json" 4 | } 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | npm-debug.log 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | build/** 2 | dist/** 3 | node_modules/** 4 | bower_components/** 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": false, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true, 14 | "globals": { 15 | "window": false, 16 | "document": false, 17 | "angular": false, 18 | "_": false, 19 | "alert": false, 20 | "app": false, 21 | "WebSocket": false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .bowerrc 2 | .gitignore 3 | .jshintignore 4 | .jshintrc 5 | .DS_Store 6 | **/.* 7 | *.yml 8 | *.xml 9 | karma.conf.js 10 | dist.sh 11 | bower.json 12 | src/ 13 | example/ 14 | test/ 15 | bower_components/ 16 | npm-debug.log 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.11' 4 | - '0.10' 5 | before_script: 6 | - npm install -g bower 7 | - bower install 8 | - export CHROME_BIN=chromium-browser 9 | - export DISPLAY=:99.0 10 | - sh -e /etc/init.d/xvfb start 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.0.0 (2016-5-21) 2 | 3 | Features: 4 | - Repackage library 5 | - Package source files no longer duplicated 6 | 7 | Bugfixes: 8 | - autoApply is undefined error 9 | - arraybuffer data type 10 | 11 | BREAKING CHANGES: 12 | - package source files are not duplicated 13 | - if using files from `/`, use `/dist/` instead 14 | - change `/angular-websocket-mock.js` to `/dist/angular-websocket-mock.js` 15 | - change `/angular-websocket.js` to `/dist/angular-websocket.js` 16 | - change `/angular-websocket-min.js` to `/dist/angular-websocket-min.js` 17 | - change `/angular-websocket-min.js.map` to `/dist/angular-websocket-min.js.map` 18 | 19 | ## 1.1.0 (2016-4-4) 20 | 21 | ## 1.0.14 (2015-9-15) 22 | 23 | ## 1.0.13 (2015-6-2) 24 | Build: 25 | - UpdatedevDependencies 26 | 27 | ## 1.0.12 (2015-6-2) 28 | 29 | Features: 30 | - Update to angular 1.3.15 31 | - Added an alwaysReconnect option 32 | - Can mock a websocket server for testing purpose. 33 | - Use Angular's `bind` polyfill 34 | 35 | Bugfixes: 36 | - Pass options to the $websocket constructor 37 | - Fix MockWebsocket readyState and flush 38 | 39 | 40 | ## 1.0.9 (2015-2-22) 41 | 42 | Features: 43 | - Export module for better browserify support 44 | 45 | Bugfixes: 46 | - Missing `ws` dependency for browserify 47 | 48 | ## 1.0.8 (2015-1-29) 49 | 50 | Features: 51 | - Allow .asyncApply() if useApplyAsync is true in config 52 | - Pass event to .notifyOpenCallbacks() callbacks 53 | - Tests for .bindToScope and .safeDigest 54 | 55 | Bugfixes: 56 | - Bind ._connect() correctly for .reconnect() 57 | - Correct rootScopeFailover of $rootScope with bindToScope 58 | 59 | ## 1.0.7 (2015-1-20) 60 | 61 | Features: 62 | - Named functions for debugging 63 | - Add .bindToScope() and expose .safeDigest() with scope/rootScopeFailover option 64 | 65 | Bugfixes: 66 | - Remove default protocol 67 | 68 | Deprecated: 69 | - Change $WebSocketBackend.createWebSocketBackend -> $WebSocketBackend.create 70 | 71 | ## 1.0.6 (2015-1-7) 72 | 73 | Features: 74 | - Include onErrorCallbacks docs 75 | - Include onCloseCallbacks docs 76 | 77 | Bugfixes: 78 | - Typo in _connect() 79 | 80 | ## 1.0.5 (2014-12-29) 81 | 82 | Features: 83 | - NPM ignore 84 | 85 | ## 1.0.4 (2014-12-29) 86 | 87 | Features: 88 | - Update bower ignore 89 | - Add more dot files 90 | - Add keywords to bower.json and package.json 91 | 92 | ## 1.0.3 (2014-12-29) 93 | 94 | Bugfixes: 95 | - Module name typo in Readme 96 | 97 | ## 1.0.2 (2014-12-29) 98 | 99 | Bugfixes: 100 | - have built files in root directory 101 | 102 | Features: 103 | - Copy dist to root in build 104 | 105 | 106 | ## 1.0.1 (2014-12-29) 107 | 108 | Bugfixes: 109 | - Outdated Dependencies 110 | 111 | ## 1.0.0 (2014-12-29) 112 | 113 | Bugfixes: 114 | - Tests 115 | 116 | Features: 117 | - Mocks 118 | - Tests with Travis CI 119 | - Module backend 120 | - Improved API 121 | - Docs 122 | - Better working examples 123 | - Reconnect 124 | - Minified with sourcemap 125 | - Legacy support 1.2.x with IE8 126 | - Upgrade tests from Jasmine 1.3 to 2.0 127 | - Update build 128 | - Include Karma for tests 129 | - Use ngSocket as base 130 | 131 | ## 0.0.5 132 | - Initial release 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2016 Patrick Stapleton, gdi2290, PatrickJS 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Angular Websocket 4 | 5 |

6 | 7 | 8 | # Angular Websocket [![Join Slack](https://img.shields.io/badge/slack-join-brightgreen.svg)](https://angularclass.com/slack-join) [![Join the chat at https://gitter.im/AngularClass/angular-websocket](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AngularClass/angular-websocket?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [![gdi2290/angular-websocket API Documentation](https://www.omniref.com/github/gdi2290/angular-websocket.png)](https://www.omniref.com/github/gdi2290/angular-websocket) 9 | 10 | [![Travis](https://img.shields.io/travis/gdi2290/angular-websocket.svg?style=flat)](https://travis-ci.org/gdi2290/angular-websocket) 11 | [![Bower](https://img.shields.io/bower/v/angular-websocket.svg?style=flat)](https://github.com/gdi2290/angular-websocket) 12 | [![npm](https://img.shields.io/npm/v/angular-websocket.svg?style=flat)](https://www.npmjs.com/package/angular-websocket) 13 | [![Dependency Status](https://david-dm.org/gdi2290/angular-websocket.svg)](https://david-dm.org/gdi2290/angular-websocket) 14 | [![devDependency Status](https://david-dm.org/gdi2290/angular-websocket/dev-status.svg)](https://david-dm.org/gdi2290/angular-websocket#info=devDependencies) 15 | [![NPM](https://nodei.co/npm/angular-websocket.svg?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/angular-websocket/) 16 | 17 | ### Status: Looking for feedback about new API changes 18 | 19 | An AngularJS 1.x WebSocket service for connecting client applications to servers. 20 | 21 | ## How do I add this to my project? 22 | 23 | You can download angular-websocket by: 24 | 25 | * (prefered) Using bower and running `bower install angular-websocket --save` 26 | * Using npm and running `npm install angular-websocket --save` 27 | * Downloading it manually by clicking [here to download development unminified version](https://raw.github.com/AngularClass/angular-websocket/master/dist/angular-websocket.js) 28 | * CDN for development `https://cdn.rawgit.com/AngularClass/angular-websocket/v2.0.0/dist/angular-websocket.js` 29 | * CDN for production `https://cdn.rawgit.com/AngularClass/angular-websocket/v2.0.0/dist/angular-websocket.min.js` 30 | 31 | ## Usage 32 | 33 | ```html 34 | 35 | 36 |
37 | 40 |
41 | 69 | ``` 70 | 71 | ## API 72 | 73 | ### Factory: `$websocket` (in module `ngWebSocket`) 74 | 75 | returns instance of $Websocket 76 | 77 | ### Methods 78 | 79 | name | arguments | description 80 | ------------|--------------------------------------------------------|------------ 81 | $websocket
_constructor_ | url:String | Creates and opens a [WebSocket](http://mdn.io/API/WebSocket) instance.
`var ws = $websocket('ws://foo');` 82 | send | data:String,Object returns | Adds data to a queue, and attempts to send if socket is ready. Accepts string or object, and will stringify objects before sending to socket. 83 | onMessage | callback:Function
options{filter:String,RegExp, autoApply:Boolean=true} | Register a callback to be fired on every message received from the websocket, or optionally just when the message's `data` property matches the filter provided in the options object. Each message handled will safely call `$rootScope.$digest()` unless `autoApply` is set to `false in the options. Callback gets called with a [MessageEvent](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent?redirectlocale=en-US&redirectslug=WebSockets%2FWebSockets_reference%2FMessageEvent) object. 84 | onOpen | callback:Function | Function to be executed each time a socket connection is opened for this instance. 85 | onClose | callback:Function | Function to be executed each time a socket connection is closed for this instance. 86 | onError | callback:Function | Function to be executed each time a socket connection has an Error for this instance. 87 | close | force:Boolean:_optional_ | Close the underlying socket, as long as no data is still being sent from the client. Optionally force close, even if data is still being sent, by passing `true` as the `force` parameter. To check if data is being sent, read the value of `socket.bufferedAmount`. 88 | 89 | ### Properties 90 | name | type | description 91 | -------------------|------------------|------------ 92 | socket | window.WebSocket | [WebSocket](http://mdn.io/API/WebSocket) instance. 93 | sendQueue | Array | Queue of `send` calls to be made on socket when socket is able to receive data. List is populated by calls to the `send` method, but this array can be spliced if data needs to be manually removed before it's been sent to a socket. Data is removed from the array after it's been sent to the socket. 94 | onOpenCallbacks | Array | List of callbacks to be executed when the socket is opened, initially or on re-connection after broken connection. Callbacks should be added to this list through the `onOpen` method. 95 | onMessageCallbacks | Array | List of callbacks to be executed when a message is received from the socket. Callbacks should be added via the `onMessage` method. 96 | onErrorCallbacks | Array | List of callbacks to be executed when an error is received from the socket. Callbacks should be added via the `onError` method. 97 | onCloseCallbacks | Array | List of callbacks to be executed when the socket is closed. Callbacks should be added via the `onClose` method. 98 | readyState | Number:readonly | Returns either the readyState value from the underlying WebSocket instance, or a proprietary value representing the internal state of the lib, e.g. if the lib is in a state of re-connecting. 99 | initialTimeout | Number | The initial timeout, should be set at the outer limits of expected response time for the service. For example, if your service responds in 1ms on average but in 10ms for 99% of requests, then set to 10ms. 100 | maxTimeout | Number | Should be as low as possible to keep your customers happy, but high enough that the system can definitely handle requests from all clients at that sustained rate. 101 | 102 | ### CancelablePromise 103 | 104 | This type is returned from the `send()` instance method of $websocket, inherits from [$q.defer().promise](https://ng-click.com/$q). 105 | 106 | ### Methods 107 | 108 | name | arguments | description 109 | ------------|--------------------------------------------------------|------------ 110 | cancel | | Alias to `deferred.reject()`, allows preventing an unsent message from being sent to socket for any arbitrary reason. 111 | then | resolve:Function, reject:Function | Resolves when message has been passed to socket, presuming the socket has a `readyState` of 1. Rejects if the socket is hopelessly disconnected now or in the future (i.e. the library is no longer attempting to reconnect). All messages are immediately rejected when the library has determined that re-establishing a connection is unlikely. 112 | 113 | 114 | ### Service: `$websocketBackend` (in module `ngWebSocketMock`) 115 | 116 | Similar to [`httpBackend`](https://ng-click.com/$httpBackend) mock in 117 | AngularJS's `ngMock` module. You can use `ngWebSocketMock` to mock a websocket 118 | server in order to test your applications: 119 | 120 | ```javascript 121 | var $websocketBackend; 122 | 123 | beforeEach(angular.mock.module('ngWebSocket', 'ngWebSocketMock'); 124 | 125 | beforeEach(inject(function (_$websocketBackend_) { 126 | $websocketBackend = _$websocketBackend_; 127 | 128 | $websocketBackend.mock(); 129 | $websocketBackend.expectConnect('ws://localhost:8080/api'); 130 | $websocketBackend.expectSend({data: JSON.stringify({test: true})}); 131 | })); 132 | ``` 133 | 134 | ### Methods 135 | 136 | name | arguments | description 137 | -------------------------------|------------|----------------------------------- 138 | flush | | Executes all pending requests 139 | expectConnect | url:String | Specify the url of an expected WebSocket connection 140 | expectClose | | Expect "close" to be called on the WebSocket 141 | expectSend | msg:String | Expectation of send to be called, with required message 142 | verifyNoOutstandingExpectation | | Makes sure all expectations have been satisfied, should be called in afterEach 143 | verifyNoOutstandingRequest | | Makes sure no requests are pending, should be called in afterEach 144 | 145 | ## Frequently asked questions 146 | 147 | * *Q.*: What if the browser doesn't support WebSockets? 148 | * *A.*: This module will not help; it does not have a fallback story for browsers that do not support WebSockets. Please check your browser target support [here](http://caniuse.com/#feat=websockets) and to include fallback support. 149 | 150 | ## Development 151 | 152 | ```shell 153 | $ npm install 154 | $ bower install 155 | ``` 156 | 157 | ## Changelog 158 | [Changelog](https://github.com/gdi2290/angular-websocket/blob/master/CHANGELOG.md) 159 | 160 | ### Unit Tests 161 | `$ npm test` Run karma in Chrome, Firefox, and Safari 162 | 163 | ### Manual Tests 164 | 165 | In the project root directory open `index.html` in the example folder or browserify example 166 | 167 | ### Distribute 168 | `$ npm run dist` Builds files with uglifyjs 169 | 170 | ### Support, Questions, or Feedback 171 | > Contact us anytime for anything about this repo or Angular 2 172 | 173 | * [Slack: AngularClass](https://angularclass.com/slack-join) 174 | * [Gitter: angularclass/angular-websocket](https://gitter.im/AngularClass/angular-websocket) 175 | * [Twitter: @AngularClass](https://twitter.com/AngularClass) 176 | 177 | 178 | ## TODO 179 | * Allow JSON if object is sent 180 | * Allow more control over $digest cycle per WebSocket instance 181 | * Add Angular interceptors 182 | * Add .on(event) 183 | * Include more examples of patterns for realtime Angular apps 184 | * Allow for optional configuration object in $websocket constructor 185 | * Add W3C Websocket support 186 | * Add socket.io support 187 | * Add SockJS support 188 | * Add Faye support 189 | * Add PubNub support 190 | ___ 191 | 192 | enjoy — **AngularClass** 193 | 194 |

195 | 196 | [![AngularClass](https://cloud.githubusercontent.com/assets/1016365/9863770/cb0620fc-5af7-11e5-89df-d4b0b2cdfc43.png "Angular Class")](https://angularclass.com) 197 | ##[AngularClass](https://angularclass.com) 198 | > Learn AngularJS, Angular 2, and Modern Web Development form the best. 199 | > Looking for corporate Angular training, want to host us, or Angular consulting? patrick@angularclass.com 200 | 201 | 202 | ## License 203 | [MIT](https://github.com/angularclass/angular-websocket/blob/master/LICENSE) 204 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-websocket", 3 | "version": "2.0.0", 4 | "homepage": "https://github.com/gdi2290/angular-websocket", 5 | "description": "An AngularJS 1.x WebSocket service for connecting client applications to servers.", 6 | "keywords": [ 7 | "angular", 8 | "angularjs", 9 | "javascript", 10 | "realtime", 11 | "websockets", 12 | "websocket", 13 | "angular-websocket", 14 | "angular-websockets", 15 | "angular-socket", 16 | "ngWebSocket", 17 | "ngWebSockets", 18 | "gdi2290", 19 | "PatrickJS" 20 | ], 21 | "authors": [ 22 | "gdi2290 " 23 | ], 24 | "main": "./dist/angular-websocket.js", 25 | "ignore": [ 26 | ".bowerrc", 27 | ".gitignore", 28 | ".npmignore", 29 | ".jshintignore", 30 | ".jshintrc", 31 | "karma.conf.js", 32 | "test.sh", 33 | "dist.sh", 34 | "src", 35 | "example", 36 | "package.json", 37 | "**/.*", 38 | "*.yml", 39 | "*.xml", 40 | "node_modules", 41 | "bower_components", 42 | "test", 43 | "tests" 44 | ], 45 | "dependencies": { 46 | "angular": "*" 47 | }, 48 | "devDependencies": { 49 | "angular": "~1.3.15", 50 | "angular-mocks": "~1.3.15" 51 | }, 52 | "resolutions": { 53 | "angular": "~1.3.15" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /dist/angular-websocket-mock.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | if (typeof define === "function" && define.amd) { 3 | define(['module', 'angular'], factory); 4 | } else if (typeof exports !== "undefined") { 5 | factory(module, require('angular')); 6 | } else { 7 | var mod = { 8 | exports: {} 9 | }; 10 | factory(mod, global.angular); 11 | global.angularWebsocketMock = mod.exports; 12 | } 13 | })(this, function (module, _angular) { 14 | 'use strict'; 15 | 16 | var _angular2 = _interopRequireDefault(_angular); 17 | 18 | function _interopRequireDefault(obj) { 19 | return obj && obj.__esModule ? obj : { 20 | default: obj 21 | }; 22 | } 23 | 24 | function $WebSocketBackend() { 25 | var connectQueue = []; 26 | var pendingConnects = []; 27 | var closeQueue = []; 28 | var pendingCloses = []; 29 | var sendQueue = []; 30 | var pendingSends = []; 31 | var mock = false; 32 | 33 | function $MockWebSocket(url, protocols) { 34 | this.protocols = protocols; 35 | this.ssl = /(wss)/i.test(this.url); 36 | } 37 | 38 | $MockWebSocket.prototype.send = function (msg) { 39 | pendingSends.push(msg); 40 | }; 41 | 42 | this.mockSend = function () { 43 | if (mock) { 44 | return sendQueue.shift(); 45 | } 46 | }; 47 | 48 | this.mock = function () { 49 | mock = true; 50 | }; 51 | 52 | this.isMocked = function () { 53 | return mock; 54 | }; 55 | 56 | this.isConnected = function (url) { 57 | return connectQueue.indexOf(url) > -1; 58 | }; 59 | 60 | $MockWebSocket.prototype.close = function () { 61 | pendingCloses.push(true); 62 | }; 63 | 64 | function createWebSocketBackend(url, protocols) { 65 | pendingConnects.push(url); 66 | // pendingConnects.push({ 67 | // url: url, 68 | // protocols: protocols 69 | // }); 70 | 71 | if (protocols) { 72 | return new $MockWebSocket(url, protocols); 73 | } 74 | return new $MockWebSocket(url); 75 | } 76 | this.create = createWebSocketBackend; 77 | this.createWebSocketBackend = createWebSocketBackend; 78 | 79 | this.flush = function () { 80 | var url, msg, config; 81 | while (url = pendingConnects.shift()) { 82 | var i = connectQueue.indexOf(url); 83 | if (i > -1) { 84 | connectQueue.splice(i, 1); 85 | } 86 | // if (config && config.url) { 87 | // } 88 | } 89 | 90 | while (pendingCloses.shift()) { 91 | closeQueue.shift(); 92 | } 93 | 94 | while (msg = pendingSends.shift()) { 95 | var j; 96 | sendQueue.forEach(function (pending, i) { 97 | if (pending.message === msg.message) { 98 | j = i; 99 | } 100 | }); 101 | 102 | if (j > -1) { 103 | sendQueue.splice(j, 1); 104 | } 105 | } 106 | }; 107 | 108 | this.expectConnect = function (url, protocols) { 109 | connectQueue.push(url); 110 | // connectQueue.push({url: url, protocols: protocols}); 111 | }; 112 | 113 | this.expectClose = function () { 114 | closeQueue.push(true); 115 | }; 116 | 117 | this.expectSend = function (msg) { 118 | sendQueue.push(msg); 119 | }; 120 | 121 | this.verifyNoOutstandingExpectation = function () { 122 | if (connectQueue.length || closeQueue.length || sendQueue.length) { 123 | throw new Error('Requests waiting to be flushed'); 124 | } 125 | }; 126 | 127 | this.verifyNoOutstandingRequest = function () { 128 | if (pendingConnects.length || pendingCloses.length || pendingSends.length) { 129 | throw new Error('Requests waiting to be processed'); 130 | } 131 | }; 132 | } // end $WebSocketBackend 133 | 134 | _angular2.default.module('ngWebSocketMock', []).service('WebSocketBackend', $WebSocketBackend).service('$websocketBackend', $WebSocketBackend); 135 | 136 | _angular2.default.module('angular-websocket-mock', ['ngWebSocketMock']); 137 | 138 | module.exports = _angular2.default.module('ngWebSocketMock'); 139 | }); -------------------------------------------------------------------------------- /dist/angular-websocket.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | if (typeof define === "function" && define.amd) { 3 | define(['module', 'exports', 'angular', 'ws'], factory); 4 | } else if (typeof exports !== "undefined") { 5 | factory(module, exports, require('angular'), require('ws')); 6 | } else { 7 | var mod = { 8 | exports: {} 9 | }; 10 | factory(mod, mod.exports, global.angular, global.ws); 11 | global.angularWebsocket = mod.exports; 12 | } 13 | })(this, function (module, exports, _angular, ws) { 14 | 'use strict'; 15 | 16 | Object.defineProperty(exports, "__esModule", { 17 | value: true 18 | }); 19 | 20 | var _angular2 = _interopRequireDefault(_angular); 21 | 22 | function _interopRequireDefault(obj) { 23 | return obj && obj.__esModule ? obj : { 24 | default: obj 25 | }; 26 | } 27 | 28 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { 29 | return typeof obj; 30 | } : function (obj) { 31 | return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; 32 | }; 33 | 34 | var Socket; 35 | 36 | if (typeof window === 'undefined') { 37 | try { 38 | 39 | Socket = ws.Client || ws.client || ws; 40 | } catch (e) {} 41 | } 42 | 43 | // Browser 44 | Socket = Socket || window.WebSocket || window.MozWebSocket; 45 | 46 | var noop = _angular2.default.noop; 47 | var objectFreeze = Object.freeze ? Object.freeze : noop; 48 | var objectDefineProperty = Object.defineProperty; 49 | var isString = _angular2.default.isString; 50 | var isFunction = _angular2.default.isFunction; 51 | var isDefined = _angular2.default.isDefined; 52 | var isObject = _angular2.default.isObject; 53 | var isArray = _angular2.default.isArray; 54 | var forEach = _angular2.default.forEach; 55 | var arraySlice = Array.prototype.slice; 56 | // ie8 wat 57 | if (!Array.prototype.indexOf) { 58 | Array.prototype.indexOf = function (elt /*, from*/) { 59 | var len = this.length >>> 0; 60 | var from = Number(arguments[1]) || 0; 61 | from = from < 0 ? Math.ceil(from) : Math.floor(from); 62 | if (from < 0) { 63 | from += len; 64 | } 65 | 66 | for (; from < len; from++) { 67 | if (from in this && this[from] === elt) { 68 | return from; 69 | } 70 | } 71 | return -1; 72 | }; 73 | } 74 | 75 | // $WebSocketProvider.$inject = ['$rootScope', '$q', '$timeout', '$websocketBackend']; 76 | function $WebSocketProvider($rootScope, $q, $timeout, $websocketBackend) { 77 | 78 | function $WebSocket(url, protocols, options) { 79 | if (!options && isObject(protocols) && !isArray(protocols)) { 80 | options = protocols; 81 | protocols = undefined; 82 | } 83 | 84 | this.protocols = protocols; 85 | this.url = url || 'Missing URL'; 86 | this.ssl = /(wss)/i.test(this.url); 87 | 88 | // this.binaryType = ''; 89 | // this.extensions = ''; 90 | // this.bufferedAmount = 0; 91 | // this.trasnmitting = false; 92 | // this.buffer = []; 93 | 94 | // TODO: refactor options to use isDefined 95 | this.scope = options && options.scope || $rootScope; 96 | this.rootScopeFailover = options && options.rootScopeFailover && true; 97 | this.useApplyAsync = options && options.useApplyAsync || false; 98 | this.initialTimeout = options && options.initialTimeout || 500; // 500ms 99 | this.maxTimeout = options && options.maxTimeout || 5 * 60 * 1000; // 5 minutes 100 | this.reconnectIfNotNormalClose = options && options.reconnectIfNotNormalClose || false; 101 | this.binaryType = options && options.binaryType || 'blob'; 102 | 103 | this._reconnectAttempts = 0; 104 | this.sendQueue = []; 105 | this.onOpenCallbacks = []; 106 | this.onMessageCallbacks = []; 107 | this.onErrorCallbacks = []; 108 | this.onCloseCallbacks = []; 109 | 110 | objectFreeze(this._readyStateConstants); 111 | 112 | if (url) { 113 | this._connect(); 114 | } else { 115 | this._setInternalState(0); 116 | } 117 | } 118 | 119 | $WebSocket.prototype._readyStateConstants = { 120 | 'CONNECTING': 0, 121 | 'OPEN': 1, 122 | 'CLOSING': 2, 123 | 'CLOSED': 3, 124 | 'RECONNECT_ABORTED': 4 125 | }; 126 | 127 | $WebSocket.prototype._normalCloseCode = 1000; 128 | 129 | $WebSocket.prototype._reconnectableStatusCodes = [4000]; 130 | 131 | $WebSocket.prototype.safeDigest = function safeDigest(autoApply) { 132 | if (autoApply && !this.scope.$$phase) { 133 | this.scope.$digest(); 134 | } 135 | }; 136 | 137 | $WebSocket.prototype.bindToScope = function bindToScope(scope) { 138 | var self = this; 139 | if (scope) { 140 | this.scope = scope; 141 | if (this.rootScopeFailover) { 142 | this.scope.$on('$destroy', function () { 143 | self.scope = $rootScope; 144 | }); 145 | } 146 | } 147 | return self; 148 | }; 149 | 150 | $WebSocket.prototype._connect = function _connect(force) { 151 | if (force || !this.socket || this.socket.readyState !== this._readyStateConstants.OPEN) { 152 | this.socket = $websocketBackend.create(this.url, this.protocols); 153 | this.socket.onmessage = _angular2.default.bind(this, this._onMessageHandler); 154 | this.socket.onopen = _angular2.default.bind(this, this._onOpenHandler); 155 | this.socket.onerror = _angular2.default.bind(this, this._onErrorHandler); 156 | this.socket.onclose = _angular2.default.bind(this, this._onCloseHandler); 157 | this.socket.binaryType = this.binaryType; 158 | } 159 | }; 160 | 161 | $WebSocket.prototype.fireQueue = function fireQueue() { 162 | while (this.sendQueue.length && this.socket.readyState === this._readyStateConstants.OPEN) { 163 | var data = this.sendQueue.shift(); 164 | 165 | this.socket.send(isString(data.message) || this.binaryType != 'blob' ? data.message : JSON.stringify(data.message)); 166 | data.deferred.resolve(); 167 | } 168 | }; 169 | 170 | $WebSocket.prototype.notifyOpenCallbacks = function notifyOpenCallbacks(event) { 171 | for (var i = 0; i < this.onOpenCallbacks.length; i++) { 172 | this.onOpenCallbacks[i].call(this, event); 173 | } 174 | }; 175 | 176 | $WebSocket.prototype.notifyCloseCallbacks = function notifyCloseCallbacks(event) { 177 | for (var i = 0; i < this.onCloseCallbacks.length; i++) { 178 | this.onCloseCallbacks[i].call(this, event); 179 | } 180 | }; 181 | 182 | $WebSocket.prototype.notifyErrorCallbacks = function notifyErrorCallbacks(event) { 183 | for (var i = 0; i < this.onErrorCallbacks.length; i++) { 184 | this.onErrorCallbacks[i].call(this, event); 185 | } 186 | }; 187 | 188 | $WebSocket.prototype.onOpen = function onOpen(cb) { 189 | this.onOpenCallbacks.push(cb); 190 | return this; 191 | }; 192 | 193 | $WebSocket.prototype.onClose = function onClose(cb) { 194 | this.onCloseCallbacks.push(cb); 195 | return this; 196 | }; 197 | 198 | $WebSocket.prototype.onError = function onError(cb) { 199 | this.onErrorCallbacks.push(cb); 200 | return this; 201 | }; 202 | 203 | $WebSocket.prototype.onMessage = function onMessage(callback, options) { 204 | if (!isFunction(callback)) { 205 | throw new Error('Callback must be a function'); 206 | } 207 | 208 | if (options && isDefined(options.filter) && !isString(options.filter) && !(options.filter instanceof RegExp)) { 209 | throw new Error('Pattern must be a string or regular expression'); 210 | } 211 | 212 | this.onMessageCallbacks.push({ 213 | fn: callback, 214 | pattern: options ? options.filter : undefined, 215 | autoApply: options ? options.autoApply : true 216 | }); 217 | return this; 218 | }; 219 | 220 | $WebSocket.prototype._onOpenHandler = function _onOpenHandler(event) { 221 | this._reconnectAttempts = 0; 222 | this.notifyOpenCallbacks(event); 223 | this.fireQueue(); 224 | }; 225 | 226 | $WebSocket.prototype._onCloseHandler = function _onCloseHandler(event) { 227 | var self = this; 228 | if (self.useApplyAsync) { 229 | self.scope.$applyAsync(function () { 230 | self.notifyCloseCallbacks(event); 231 | }); 232 | } else { 233 | self.notifyCloseCallbacks(event); 234 | self.safeDigest(true); 235 | } 236 | if (this.reconnectIfNotNormalClose && event.code !== this._normalCloseCode || this._reconnectableStatusCodes.indexOf(event.code) > -1) { 237 | this.reconnect(); 238 | } 239 | }; 240 | 241 | $WebSocket.prototype._onErrorHandler = function _onErrorHandler(event) { 242 | var self = this; 243 | if (self.useApplyAsync) { 244 | self.scope.$applyAsync(function () { 245 | self.notifyErrorCallbacks(event); 246 | }); 247 | } else { 248 | self.notifyErrorCallbacks(event); 249 | self.safeDigest(true); 250 | } 251 | }; 252 | 253 | $WebSocket.prototype._onMessageHandler = function _onMessageHandler(message) { 254 | var pattern; 255 | var self = this; 256 | var currentCallback; 257 | for (var i = 0; i < self.onMessageCallbacks.length; i++) { 258 | currentCallback = self.onMessageCallbacks[i]; 259 | pattern = currentCallback.pattern; 260 | if (pattern) { 261 | if (isString(pattern) && message.data === pattern) { 262 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); 263 | } else if (pattern instanceof RegExp && pattern.exec(message.data)) { 264 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); 265 | } 266 | } else { 267 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); 268 | } 269 | } 270 | 271 | function applyAsyncOrDigest(callback, autoApply, args) { 272 | args = arraySlice.call(arguments, 2); 273 | if (self.useApplyAsync) { 274 | self.scope.$applyAsync(function () { 275 | callback.apply(self, args); 276 | }); 277 | } else { 278 | callback.apply(self, args); 279 | self.safeDigest(autoApply); 280 | } 281 | } 282 | }; 283 | 284 | $WebSocket.prototype.close = function close(force) { 285 | if (force || !this.socket.bufferedAmount) { 286 | this.socket.close(); 287 | } 288 | return this; 289 | }; 290 | 291 | $WebSocket.prototype.send = function send(data) { 292 | var deferred = $q.defer(); 293 | var self = this; 294 | var promise = cancelableify(deferred.promise); 295 | 296 | if (self.readyState === self._readyStateConstants.RECONNECT_ABORTED) { 297 | deferred.reject('Socket connection has been closed'); 298 | } else { 299 | self.sendQueue.push({ 300 | message: data, 301 | deferred: deferred 302 | }); 303 | self.fireQueue(); 304 | } 305 | 306 | // Credit goes to @btford 307 | function cancelableify(promise) { 308 | promise.cancel = cancel; 309 | var then = promise.then; 310 | promise.then = function () { 311 | var newPromise = then.apply(this, arguments); 312 | return cancelableify(newPromise); 313 | }; 314 | return promise; 315 | } 316 | 317 | function cancel(reason) { 318 | self.sendQueue.splice(self.sendQueue.indexOf(data), 1); 319 | deferred.reject(reason); 320 | return self; 321 | } 322 | 323 | if ($websocketBackend.isMocked && $websocketBackend.isMocked() && $websocketBackend.isConnected(this.url)) { 324 | this._onMessageHandler($websocketBackend.mockSend()); 325 | } 326 | 327 | return promise; 328 | }; 329 | 330 | $WebSocket.prototype.reconnect = function reconnect() { 331 | this.close(); 332 | 333 | var backoffDelay = this._getBackoffDelay(++this._reconnectAttempts); 334 | 335 | var backoffDelaySeconds = backoffDelay / 1000; 336 | console.log('Reconnecting in ' + backoffDelaySeconds + ' seconds'); 337 | 338 | $timeout(_angular2.default.bind(this, this._connect), backoffDelay); 339 | 340 | return this; 341 | }; 342 | // Exponential Backoff Formula by Prof. Douglas Thain 343 | // http://dthain.blogspot.co.uk/2009/02/exponential-backoff-in-distributed.html 344 | $WebSocket.prototype._getBackoffDelay = function _getBackoffDelay(attempt) { 345 | var R = Math.random() + 1; 346 | var T = this.initialTimeout; 347 | var F = 2; 348 | var N = attempt; 349 | var M = this.maxTimeout; 350 | 351 | return Math.floor(Math.min(R * T * Math.pow(F, N), M)); 352 | }; 353 | 354 | $WebSocket.prototype._setInternalState = function _setInternalState(state) { 355 | if (Math.floor(state) !== state || state < 0 || state > 4) { 356 | throw new Error('state must be an integer between 0 and 4, got: ' + state); 357 | } 358 | 359 | // ie8 wat 360 | if (!objectDefineProperty) { 361 | this.readyState = state || this.socket.readyState; 362 | } 363 | this._internalConnectionState = state; 364 | 365 | forEach(this.sendQueue, function (pending) { 366 | pending.deferred.reject('Message cancelled due to closed socket connection'); 367 | }); 368 | }; 369 | 370 | // Read only .readyState 371 | if (objectDefineProperty) { 372 | objectDefineProperty($WebSocket.prototype, 'readyState', { 373 | get: function get() { 374 | return this._internalConnectionState || this.socket.readyState; 375 | }, 376 | set: function set() { 377 | throw new Error('The readyState property is read-only'); 378 | } 379 | }); 380 | } 381 | 382 | return function (url, protocols, options) { 383 | return new $WebSocket(url, protocols, options); 384 | }; 385 | } 386 | 387 | // $WebSocketBackendProvider.$inject = ['$log']; 388 | function $WebSocketBackendProvider($log) { 389 | this.create = function create(url, protocols) { 390 | var match = /wss?:\/\//.exec(url); 391 | 392 | if (!match) { 393 | throw new Error('Invalid url provided'); 394 | } 395 | 396 | if (protocols) { 397 | return new Socket(url, protocols); 398 | } 399 | 400 | return new Socket(url); 401 | }; 402 | 403 | this.createWebSocketBackend = function createWebSocketBackend(url, protocols) { 404 | $log.warn('Deprecated: Please use .create(url, protocols)'); 405 | return this.create(url, protocols); 406 | }; 407 | } 408 | 409 | _angular2.default.module('ngWebSocket', []).factory('$websocket', ['$rootScope', '$q', '$timeout', '$websocketBackend', $WebSocketProvider]).factory('WebSocket', ['$rootScope', '$q', '$timeout', 'WebsocketBackend', $WebSocketProvider]).service('$websocketBackend', ['$log', $WebSocketBackendProvider]).service('WebSocketBackend', ['$log', $WebSocketBackendProvider]); 410 | 411 | _angular2.default.module('angular-websocket', ['ngWebSocket']); 412 | 413 | exports.default = _angular2.default.module('ngWebSocket'); 414 | module.exports = exports['default']; 415 | }); 416 | -------------------------------------------------------------------------------- /dist/angular-websocket.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){if("function"==typeof define&&define.amd)define(["module","exports","angular","ws"],t);else if("undefined"!=typeof exports)t(module,exports,require("angular"),require("ws"));else{var o={exports:{}};t(o,o.exports,e.angular,e.ws),e.angularWebsocket=o.exports}}(this,function(e,t,o,n){"use strict";function s(e){return e&&e.__esModule?e:{"default":e}}function r(e,t,o,n){function s(t,o,n){n||!k(o)||C(o)||(n=o,o=void 0),this.protocols=o,this.url=t||"Missing URL",this.ssl=/(wss)/i.test(this.url),this.scope=n&&n.scope||e,this.rootScopeFailover=n&&n.rootScopeFailover&&!0,this.useApplyAsync=n&&n.useApplyAsync||!1,this.initialTimeout=n&&n.initialTimeout||500,this.maxTimeout=n&&n.maxTimeout||3e5,this.reconnectIfNotNormalClose=n&&n.reconnectIfNotNormalClose||!1,this.binaryType=n&&n.binaryType||"blob",this._reconnectAttempts=0,this.sendQueue=[],this.onOpenCallbacks=[],this.onMessageCallbacks=[],this.onErrorCallbacks=[],this.onCloseCallbacks=[],f(this._readyStateConstants),t?this._connect():this._setInternalState(0)}return s.prototype._readyStateConstants={CONNECTING:0,OPEN:1,CLOSING:2,CLOSED:3,RECONNECT_ABORTED:4},s.prototype._normalCloseCode=1e3,s.prototype._reconnectableStatusCodes=[4e3],s.prototype.safeDigest=function(e){e&&!this.scope.$$phase&&this.scope.$digest()},s.prototype.bindToScope=function(t){var o=this;return t&&(this.scope=t,this.rootScopeFailover&&this.scope.$on("$destroy",function(){o.scope=e})),o},s.prototype._connect=function(e){!e&&this.socket&&this.socket.readyState===this._readyStateConstants.OPEN||(this.socket=n.create(this.url,this.protocols),this.socket.onmessage=c["default"].bind(this,this._onMessageHandler),this.socket.onopen=c["default"].bind(this,this._onOpenHandler),this.socket.onerror=c["default"].bind(this,this._onErrorHandler),this.socket.onclose=c["default"].bind(this,this._onCloseHandler),this.socket.binaryType=this.binaryType)},s.prototype.fireQueue=function(){for(;this.sendQueue.length&&this.socket.readyState===this._readyStateConstants.OPEN;){var e=this.sendQueue.shift();this.socket.send(d(e.message)||"blob"!=this.binaryType?e.message:JSON.stringify(e.message)),e.deferred.resolve()}},s.prototype.notifyOpenCallbacks=function(e){for(var t=0;t-1)&&this.reconnect()},s.prototype._onErrorHandler=function(e){var t=this;t.useApplyAsync?t.scope.$applyAsync(function(){t.notifyErrorCallbacks(e)}):(t.notifyErrorCallbacks(e),t.safeDigest(!0))},s.prototype._onMessageHandler=function(e){function t(e,t,o){o=m.call(arguments,2),s.useApplyAsync?s.scope.$applyAsync(function(){e.apply(s,o)}):(e.apply(s,o),s.safeDigest(t))}for(var o,n,s=this,r=0;re||e>4)throw new Error("state must be an integer between 0 and 4, got: "+e);h||(this.readyState=e||this.socket.readyState),this._internalConnectionState=e,g(this.sendQueue,function(e){e.deferred.reject("Message cancelled due to closed socket connection")})},h&&h(s.prototype,"readyState",{get:function(){return this._internalConnectionState||this.socket.readyState},set:function(){throw new Error("The readyState property is read-only")}}),function(e,t,o){return new s(e,t,o)}}function i(e){this.create=function(e,t){var o=/wss?:\/\//.exec(e);if(!o)throw new Error("Invalid url provided");return t?new a(e,t):new a(e)},this.createWebSocketBackend=function(t,o){return e.warn("Deprecated: Please use .create(url, protocols)"),this.create(t,o)}}Object.defineProperty(t,"__esModule",{value:!0});var a,c=s(o),l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e};if("object"===("undefined"==typeof t?"undefined":l(t))&&"function"==typeof require)try{a=n.Client||n.client||n}catch(u){}a=a||window.WebSocket||window.MozWebSocket;var p=c["default"].noop,f=Object.freeze?Object.freeze:p,h=Object.defineProperty,d=c["default"].isString,y=c["default"].isFunction,b=c["default"].isDefined,k=c["default"].isObject,C=c["default"].isArray,g=c["default"].forEach,m=Array.prototype.slice;Array.prototype.indexOf||(Array.prototype.indexOf=function(e){var t=this.length>>>0,o=Number(arguments[1])||0;for(o=0>o?Math.ceil(o):Math.floor(o),0>o&&(o+=t);t>o;o++)if(o in this&&this[o]===e)return o;return-1}),c["default"].module("ngWebSocket",[]).factory("$websocket",["$rootScope","$q","$timeout","$websocketBackend",r]).factory("WebSocket",["$rootScope","$q","$timeout","WebsocketBackend",r]).service("$websocketBackend",["$log",i]).service("WebSocketBackend",["$log",i]),c["default"].module("angular-websocket",["ngWebSocket"]),t["default"]=c["default"].module("ngWebSocket"),e.exports=t["default"]}); 2 | //# sourceMappingURL=angular-websocket.min.js.map 3 | -------------------------------------------------------------------------------- /dist/angular-websocket.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["dist/angular-websocket.js"],"names":["global","factory","define","amd","exports","module","require","mod","angular","ws","angularWebsocket","this","_angular","_interopRequireDefault","obj","__esModule","default","$WebSocketProvider","$rootScope","$q","$timeout","$websocketBackend","$WebSocket","url","protocols","options","isObject","isArray","undefined","ssl","test","scope","rootScopeFailover","useApplyAsync","initialTimeout","maxTimeout","reconnectIfNotNormalClose","binaryType","_reconnectAttempts","sendQueue","onOpenCallbacks","onMessageCallbacks","onErrorCallbacks","onCloseCallbacks","objectFreeze","_readyStateConstants","_connect","_setInternalState","prototype","CONNECTING","OPEN","CLOSING","CLOSED","RECONNECT_ABORTED","_normalCloseCode","_reconnectableStatusCodes","safeDigest","autoApply","$$phase","$digest","bindToScope","self","$on","force","socket","readyState","create","onmessage","_angular2","bind","_onMessageHandler","onopen","_onOpenHandler","onerror","_onErrorHandler","onclose","_onCloseHandler","fireQueue","length","data","shift","send","isString","message","JSON","stringify","deferred","resolve","notifyOpenCallbacks","event","i","call","notifyCloseCallbacks","notifyErrorCallbacks","onOpen","cb","push","onClose","onError","onMessage","callback","isFunction","Error","isDefined","filter","RegExp","fn","pattern","$applyAsync","code","indexOf","reconnect","applyAsyncOrDigest","args","arraySlice","arguments","apply","currentCallback","exec","close","bufferedAmount","cancelableify","promise","cancel","then","newPromise","reason","splice","reject","defer","isMocked","isConnected","mockSend","backoffDelay","_getBackoffDelay","backoffDelaySeconds","console","log","attempt","R","Math","random","T","F","N","M","floor","min","pow","state","objectDefineProperty","_internalConnectionState","forEach","pending","get","set","$WebSocketBackendProvider","$log","match","Socket","createWebSocketBackend","warn","Object","defineProperty","value","_typeof","Symbol","iterator","constructor","Client","client","e","window","WebSocket","MozWebSocket","noop","freeze","Array","slice","elt","len","from","Number","ceil","service"],"mappings":"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"} -------------------------------------------------------------------------------- /example/browserify-example/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": false, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true, 14 | "globals": { 15 | "window": false, 16 | "document": false, 17 | "_": false, 18 | "alert": false, 19 | "app": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/browserify-example/app/app.js: -------------------------------------------------------------------------------- 1 | require('angular'); 2 | require('angular-animate'); 3 | var ngWebSocket = require('angular-websocket'); 4 | 5 | var filters = require('./filters'); 6 | var services = require('./services'); 7 | var controllers = require('./controllers'); 8 | 9 | module.exports = angular.module('chat', [ 10 | 'ngAnimate', 11 | ngWebSocket.name, 12 | filters.name, 13 | controllers.name, 14 | services.name 15 | ]); 16 | 17 | -------------------------------------------------------------------------------- /example/browserify-example/app/controllers.js: -------------------------------------------------------------------------------- 1 | require('angular'); 2 | 3 | function MessengerController($scope, Messages) { 4 | $scope.username = 'anonymous'; 5 | 6 | $scope.Messages = Messages; 7 | 8 | $scope.submit = function(new_message) { 9 | if (!new_message) { return; } 10 | Messages.send({ 11 | username: $scope.username, 12 | message: new_message 13 | }); 14 | $scope.new_message = ''; 15 | }; 16 | 17 | } 18 | 19 | module.exports = angular.module('app.controllers', []) 20 | .controller('MessengerController', MessengerController); 21 | -------------------------------------------------------------------------------- /example/browserify-example/app/filters.js: -------------------------------------------------------------------------------- 1 | require('angular'); 2 | 3 | function capitalize() { 4 | function capWord(txt) { 5 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 6 | } 7 | return function(input, isEveryWord) { 8 | return (!input) ? '' : (!isEveryWord) ? capWord(input) : input.replace(/([^\W_]+[^\s-]*) */g, capWord); 9 | }; 10 | } 11 | 12 | module.exports = angular.module('app.filters', []) 13 | .filter('capitalize', capitalize); 14 | -------------------------------------------------------------------------------- /example/browserify-example/app/services.js: -------------------------------------------------------------------------------- 1 | require('angular'); 2 | var ngWebSocket = require('angular-websocket'); 3 | 4 | function Messages($websocket) { 5 | var ws = $websocket('ws://echo.websocket.org/'); 6 | var collection = []; 7 | 8 | ws.onMessage(function(event) { 9 | console.log('message: ', event); 10 | var res; 11 | try { 12 | res = JSON.parse(event.data); 13 | } catch(e) { 14 | res = {'username': 'anonymous', 'message': event.data}; 15 | } 16 | 17 | collection.push({ 18 | username: res.username, 19 | content: res.message, 20 | timeStamp: event.timeStamp 21 | }); 22 | }); 23 | 24 | ws.onError(function(event) { 25 | console.log('connection Error', event); 26 | }); 27 | 28 | ws.onClose(function(event) { 29 | console.log('connection closed', event); 30 | }); 31 | 32 | ws.onOpen(function() { 33 | console.log('connection open'); 34 | ws.send('Hello World'); 35 | ws.send('again'); 36 | ws.send('and again'); 37 | }); 38 | // setTimeout(function() { 39 | // ws.close(); 40 | // }, 500) 41 | 42 | return { 43 | collection: collection, 44 | status: function() { 45 | return ws.readyState; 46 | }, 47 | send: function(message) { 48 | if (angular.isString(message)) { 49 | ws.send(message); 50 | } 51 | else if (angular.isObject(message)) { 52 | ws.send(JSON.stringify(message)); 53 | } 54 | } 55 | 56 | }; 57 | } 58 | 59 | 60 | module.exports = angular.module('app.services', [ngWebSocket.name]) 61 | .factory('Messages', Messages); 62 | 63 | -------------------------------------------------------------------------------- /example/browserify-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AngularJS WebSocket Test 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |

WebSocket Test

15 | Connection: {{ Messages.status() }} 16 |
17 | 18 | 19 | 20 |
21 |
22 | {{ message.username | capitalize }}: {{ message.timeStamp | date:'medium' }} 23 |
24 | {{ message.content }} 25 |
26 |
27 |
28 | 29 |
30 | 31 |
32 | 33 | 34 |
35 | 36 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AngularJS WebSocket Test 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |

WebSocket Test

17 | Connection: {{ Messages.status() }} 18 |
19 | 20 | 21 | 22 |
23 |
24 | {{ message.username | capitalize }}: {{ message.timeStamp | date:'medium' }} 25 |
26 | {{ message.content }} 27 |
28 |
29 |
30 | 31 |
32 | 33 |
34 | 35 | 36 |
37 | 38 | 39 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /example/scripts.js: -------------------------------------------------------------------------------- 1 | angular.module('chat', [ 2 | 'ngAnimate', 3 | 'ngWebSocket' 4 | ]) 5 | .factory('Messages', function($websocket) { 6 | var ws = $websocket('ws://echo.websocket.org/'); 7 | var collection = []; 8 | 9 | ws.onMessage(function(event) { 10 | console.log('message: ', event); 11 | var res; 12 | try { 13 | res = JSON.parse(event.data); 14 | } catch(e) { 15 | res = {'username': 'anonymous', 'message': event.data}; 16 | } 17 | 18 | collection.push({ 19 | username: res.username, 20 | content: res.message, 21 | timeStamp: event.timeStamp 22 | }); 23 | }); 24 | 25 | ws.onError(function(event) { 26 | console.log('connection Error', event); 27 | }); 28 | 29 | ws.onClose(function(event) { 30 | console.log('connection closed', event); 31 | }); 32 | 33 | ws.onOpen(function() { 34 | console.log('connection open'); 35 | ws.send('Hello World'); 36 | ws.send('again'); 37 | ws.send('and again'); 38 | }); 39 | // setTimeout(function() { 40 | // ws.close(); 41 | // }, 500) 42 | 43 | return { 44 | collection: collection, 45 | status: function() { 46 | return ws.readyState; 47 | }, 48 | send: function(message) { 49 | if (angular.isString(message)) { 50 | ws.send(message); 51 | } 52 | else if (angular.isObject(message)) { 53 | ws.send(JSON.stringify(message)); 54 | } 55 | } 56 | 57 | }; 58 | }) 59 | .controller('MessengerController', function($scope, Messages) { 60 | $scope.username = 'anonymous'; 61 | 62 | $scope.Messages = Messages; 63 | 64 | $scope.submit = function(new_message) { 65 | if (!new_message) { return; } 66 | Messages.send({ 67 | username: $scope.username, 68 | message: new_message 69 | }); 70 | $scope.new_message = ''; 71 | }; 72 | 73 | 74 | }) 75 | .filter('capitalize', function() { 76 | function capWord(txt) { 77 | return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 78 | } 79 | return function(input, isEveryWord) { 80 | return (!input) ? '' : (!isEveryWord) ? capWord(input) : input.replace(/([^\W_]+[^\s-]*) */g, capWord); 81 | }; 82 | }); 83 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Dec 25 2014 14:12:24 GMT-0800 (PST) 3 | 4 | module.exports = function(config) { 5 | 6 | var configuration = { 7 | 8 | // base path, that will be used to resolve files and exclude 9 | basePath: '', 10 | 11 | // frameworks to use 12 | frameworks: ['jasmine'], 13 | 14 | // list of files / patterns to load in the browser 15 | files: [ 16 | 'bower_components/angular/angular.js', 17 | 'bower_components/angular-mocks/angular-mocks.js', 18 | 'dist/angular-websocket.js', 19 | 'dist/angular-websocket-mock.js', 20 | 'test/angular-websocket.spec.js' 21 | ], 22 | 23 | // list of files to exclude 24 | exclude: [ 25 | 26 | ], 27 | 28 | // test results reporter to use 29 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 30 | reporters: ['progress'], 31 | 32 | // web server port 33 | port: 9876, 34 | 35 | // enable / disable colors in the output (reporters and logs) 36 | colors: true, 37 | 38 | // level of logging 39 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 40 | logLevel: config.LOG_INFO, 41 | 42 | // enable / disable watching file and executing tests whenever any file changes 43 | autoWatch: false, 44 | 45 | // Start these browsers, currently available: 46 | // - Chrome 47 | // - ChromeCanary 48 | // - Firefox 49 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 50 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 51 | // - PhantomJS 52 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 53 | browsers: [ 54 | 'Chrome', 55 | 'Firefox', 56 | 'Safari' 57 | ], 58 | 59 | customLaunchers: { 60 | 'Chrome_travis_ci': { 61 | base: 'Chrome', 62 | flags: ['--no-sandbox'] 63 | } 64 | }, 65 | 66 | // If browser does not capture in given timeout [ms], kill it 67 | captureTimeout: 60000, 68 | 69 | // Continuous Integration mode 70 | // if true, it capture browsers, run tests and exit 71 | singleRun: true 72 | }; 73 | 74 | if (process.env.TRAVIS){ 75 | configuration.browsers = ['Firefox', 'Chrome_travis_ci']; 76 | } 77 | 78 | config.set(configuration); 79 | }; 80 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-websocket", 3 | "version": "2.0.1", 4 | "main": "./dist/angular-websocket.js", 5 | "description": "An Angular WebSocket service for connecting client applications to servers.", 6 | "homepage": "https://github.com/angular-class/angular-websocket", 7 | "bugs": "https://github.com/angular-class/angular-websocket/issues", 8 | "keywords": [ 9 | "angular", 10 | "angularjs", 11 | "javascript", 12 | "realtime", 13 | "websockets", 14 | "websocket", 15 | "angular-websocket", 16 | "angular-websockets", 17 | "angular-socket", 18 | "ngWebSocket", 19 | "ngWebSockets", 20 | "angular-class", 21 | "AngularClass", 22 | "gdi2290", 23 | "PatrickJS" 24 | ], 25 | "scripts": { 26 | "pretest": "npm run dist", 27 | "test": "karma start karma.conf.js", 28 | "predist": "rimraf dist/ && mkdirp dist/", 29 | "dist": "babel src/ --out-dir dist/", 30 | "postdist": "uglifyjs dist/angular-websocket.js > dist/angular-websocket.min.js --source-map dist/angular-websocket.min.js.map --source-map-url angular-websocket.min.js.map --mangle --compress --stats", 31 | "example": "browserify --debug -t browserify-ngannotate ./example/browserify-example/app/app.js --outfile ./example/browserify-example/bundle.js" 32 | }, 33 | "author": { 34 | "name": "Patrick Stapleton", 35 | "email": "github@gdi2290.com", 36 | "url": "www.gdi2290.com" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git@github.com:angular-class/angular-websocket.git" 41 | }, 42 | "license": "MIT", 43 | "dependencies": { 44 | "angular": "*", 45 | "ws": "^1.1.0" 46 | }, 47 | "devDependencies": { 48 | "angular-animate": "^1.3.13", 49 | "babel-cli": "^6.7.5", 50 | "babel-plugin-add-module-exports": "^0.1.2", 51 | "babel-plugin-transform-es2015-modules-umd": "^6.6.5", 52 | "babel-preset-es2015": "^6.6.0", 53 | "browserify": "^13.0.0", 54 | "browserify-ngannotate": "^2.0.0", 55 | "bufferutil": "^1.2.1", 56 | "jasmine-core": "^2.3.4", 57 | "karma": "^0.13.22", 58 | "karma-chrome-launcher": "^0.2.3", 59 | "karma-firefox-launcher": "^0.1.4", 60 | "karma-jasmine": "^0.3.5", 61 | "karma-phantomjs-launcher": "^1.0.0", 62 | "karma-requirejs": "~0.2.1", 63 | "karma-safari-launcher": "^0.1.1", 64 | "karma-script-launcher": "^0.2.0", 65 | "karma-slimerjs-launcher": "^0.2.0", 66 | "mkdirp": "^0.5.1", 67 | "ng-annotate": "^1.0.0", 68 | "phantomjs": "^2.1.3", 69 | "phantomjs-prebuilt": "^2.1.7", 70 | "requirejs": "^2.1.16", 71 | "rimraf": "^2.5.2", 72 | "uglify-js": "^2.4.16", 73 | "utf-8-validate": "^1.2.1" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/angular-websocket-mock.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | function $WebSocketBackend() { 4 | var connectQueue = []; 5 | var pendingConnects = []; 6 | var closeQueue = []; 7 | var pendingCloses = []; 8 | var sendQueue = []; 9 | var pendingSends = []; 10 | var mock = false; 11 | 12 | 13 | function $MockWebSocket(url, protocols) { 14 | this.protocols = protocols; 15 | this.ssl = /(wss)/i.test(this.url); 16 | 17 | } 18 | 19 | $MockWebSocket.prototype.send = function (msg) { 20 | pendingSends.push(msg); 21 | }; 22 | 23 | this.mockSend = function() { 24 | if (mock) { 25 | return sendQueue.shift(); 26 | } 27 | }; 28 | 29 | this.mock = function() { 30 | mock = true; 31 | }; 32 | 33 | this.isMocked = function () { 34 | return mock; 35 | }; 36 | 37 | this.isConnected = function(url) { 38 | return connectQueue.indexOf(url) > -1; 39 | }; 40 | 41 | $MockWebSocket.prototype.close = function () { 42 | pendingCloses.push(true); 43 | }; 44 | 45 | function createWebSocketBackend(url, protocols) { 46 | pendingConnects.push(url); 47 | // pendingConnects.push({ 48 | // url: url, 49 | // protocols: protocols 50 | // }); 51 | 52 | if (protocols) { 53 | return new $MockWebSocket(url, protocols); 54 | } 55 | return new $MockWebSocket(url); 56 | } 57 | this.create = createWebSocketBackend; 58 | this.createWebSocketBackend = createWebSocketBackend; 59 | 60 | this.flush = function () { 61 | var url, msg, config; 62 | while (url = pendingConnects.shift()) { 63 | var i = connectQueue.indexOf(url); 64 | if (i > -1) { 65 | connectQueue.splice(i, 1); 66 | } 67 | // if (config && config.url) { 68 | // } 69 | } 70 | 71 | while (pendingCloses.shift()) { 72 | closeQueue.shift(); 73 | } 74 | 75 | while (msg = pendingSends.shift()) { 76 | var j; 77 | sendQueue.forEach(function(pending, i) { 78 | if (pending.message === msg.message) { 79 | j = i; 80 | } 81 | }); 82 | 83 | if (j > -1) { 84 | sendQueue.splice(j, 1); 85 | } 86 | } 87 | }; 88 | 89 | this.expectConnect = function (url, protocols) { 90 | connectQueue.push(url); 91 | // connectQueue.push({url: url, protocols: protocols}); 92 | }; 93 | 94 | this.expectClose = function () { 95 | closeQueue.push(true); 96 | }; 97 | 98 | this.expectSend = function (msg) { 99 | sendQueue.push(msg); 100 | }; 101 | 102 | this.verifyNoOutstandingExpectation = function () { 103 | if (connectQueue.length || closeQueue.length || sendQueue.length) { 104 | throw new Error('Requests waiting to be flushed'); 105 | } 106 | }; 107 | 108 | this.verifyNoOutstandingRequest = function () { 109 | if (pendingConnects.length || pendingCloses.length || pendingSends.length) { 110 | throw new Error('Requests waiting to be processed'); 111 | } 112 | }; 113 | 114 | } // end $WebSocketBackend 115 | 116 | angular.module('ngWebSocketMock', []) 117 | .service('WebSocketBackend', $WebSocketBackend) 118 | .service('$websocketBackend', $WebSocketBackend); 119 | 120 | angular.module('angular-websocket-mock', ['ngWebSocketMock']); 121 | 122 | module.exports = angular.module('ngWebSocketMock'); 123 | -------------------------------------------------------------------------------- /src/angular-websocket.js: -------------------------------------------------------------------------------- 1 | import angular from 'angular'; 2 | 3 | var Socket; 4 | 5 | if (typeof window === 'undefined') { 6 | try { 7 | var ws = require('ws'); 8 | 9 | Socket = (ws.Client || ws.client || ws); 10 | } catch(e) {} 11 | } 12 | 13 | // Browser 14 | Socket = (Socket || window.WebSocket || window.MozWebSocket); 15 | 16 | var noop = angular.noop; 17 | var objectFreeze = (Object.freeze) ? Object.freeze : noop; 18 | var objectDefineProperty = Object.defineProperty; 19 | var isString = angular.isString; 20 | var isFunction = angular.isFunction; 21 | var isDefined = angular.isDefined; 22 | var isObject = angular.isObject; 23 | var isArray = angular.isArray; 24 | var forEach = angular.forEach; 25 | var arraySlice = Array.prototype.slice; 26 | // ie8 wat 27 | if (!Array.prototype.indexOf) { 28 | Array.prototype.indexOf = function(elt /*, from*/) { 29 | var len = this.length >>> 0; 30 | var from = Number(arguments[1]) || 0; 31 | from = (from < 0) ? Math.ceil(from) : Math.floor(from); 32 | if (from < 0) { 33 | from += len; 34 | } 35 | 36 | for (; from < len; from++) { 37 | if (from in this && this[from] === elt) { return from; } 38 | } 39 | return -1; 40 | }; 41 | } 42 | 43 | // $WebSocketProvider.$inject = ['$rootScope', '$q', '$timeout', '$websocketBackend']; 44 | function $WebSocketProvider($rootScope, $q, $timeout, $websocketBackend) { 45 | 46 | function $WebSocket(url, protocols, options, wsOptions = {}) { 47 | if (!options && isObject(protocols) && !isArray(protocols)) { 48 | options = protocols; 49 | protocols = undefined; 50 | } 51 | this.wsOptions = wsOptions; 52 | this.protocols = protocols; 53 | this.url = url || 'Missing URL'; 54 | this.ssl = /(wss)/i.test(this.url); 55 | 56 | // this.binaryType = ''; 57 | // this.extensions = ''; 58 | // this.bufferedAmount = 0; 59 | // this.trasnmitting = false; 60 | // this.buffer = []; 61 | 62 | // TODO: refactor options to use isDefined 63 | this.scope = options && options.scope || $rootScope; 64 | this.rootScopeFailover = options && options.rootScopeFailover && true; 65 | this.useApplyAsync = options && options.useApplyAsync || false; 66 | this.initialTimeout = options && options.initialTimeout || 500; // 500ms 67 | this.maxTimeout = options && options.maxTimeout || 5 * 60 * 1000; // 5 minutes 68 | this.reconnectIfNotNormalClose = options && options.reconnectIfNotNormalClose || false; 69 | this.binaryType = options && options.binaryType || 'blob'; 70 | 71 | this._reconnectAttempts = 0; 72 | this.sendQueue = []; 73 | this.onOpenCallbacks = []; 74 | this.onMessageCallbacks = []; 75 | this.onErrorCallbacks = []; 76 | this.onCloseCallbacks = []; 77 | 78 | objectFreeze(this._readyStateConstants); 79 | 80 | if (url) { 81 | this._connect(); 82 | } else { 83 | this._setInternalState(0); 84 | } 85 | 86 | } 87 | 88 | 89 | $WebSocket.prototype._readyStateConstants = { 90 | 'CONNECTING': 0, 91 | 'OPEN': 1, 92 | 'CLOSING': 2, 93 | 'CLOSED': 3, 94 | 'RECONNECT_ABORTED': 4 95 | }; 96 | 97 | $WebSocket.prototype._normalCloseCode = 1000; 98 | 99 | $WebSocket.prototype._reconnectableStatusCodes = [ 100 | 4000 101 | ]; 102 | 103 | $WebSocket.prototype.safeDigest = function safeDigest(autoApply) { 104 | if (autoApply && !this.scope.$$phase) { 105 | this.scope.$digest(); 106 | } 107 | }; 108 | 109 | $WebSocket.prototype.bindToScope = function bindToScope(scope) { 110 | var self = this; 111 | if (scope) { 112 | this.scope = scope; 113 | if (this.rootScopeFailover) { 114 | this.scope.$on('$destroy', function() { 115 | self.scope = $rootScope; 116 | }); 117 | } 118 | } 119 | return self; 120 | }; 121 | 122 | $WebSocket.prototype._connect = function _connect(force) { 123 | if (force || !this.socket || this.socket.readyState !== this._readyStateConstants.OPEN) { 124 | this.socket = $websocketBackend.create(this.url, this.protocols, this.wsOptions); 125 | this.socket.onmessage = angular.bind(this, this._onMessageHandler); 126 | this.socket.onopen = angular.bind(this, this._onOpenHandler); 127 | this.socket.onerror = angular.bind(this, this._onErrorHandler); 128 | this.socket.onclose = angular.bind(this, this._onCloseHandler); 129 | this.socket.binaryType = this.binaryType; 130 | } 131 | }; 132 | 133 | $WebSocket.prototype.fireQueue = function fireQueue() { 134 | while (this.sendQueue.length && this.socket.readyState === this._readyStateConstants.OPEN) { 135 | var data = this.sendQueue.shift(); 136 | 137 | this.socket.send( 138 | isString(data.message) || this.binaryType != 'blob' ? data.message : JSON.stringify(data.message) 139 | ); 140 | data.deferred.resolve(); 141 | } 142 | }; 143 | 144 | $WebSocket.prototype.notifyOpenCallbacks = function notifyOpenCallbacks(event) { 145 | for (var i = 0; i < this.onOpenCallbacks.length; i++) { 146 | this.onOpenCallbacks[i].call(this, event); 147 | } 148 | }; 149 | 150 | $WebSocket.prototype.notifyCloseCallbacks = function notifyCloseCallbacks(event) { 151 | for (var i = 0; i < this.onCloseCallbacks.length; i++) { 152 | this.onCloseCallbacks[i].call(this, event); 153 | } 154 | }; 155 | 156 | $WebSocket.prototype.notifyErrorCallbacks = function notifyErrorCallbacks(event) { 157 | for (var i = 0; i < this.onErrorCallbacks.length; i++) { 158 | this.onErrorCallbacks[i].call(this, event); 159 | } 160 | }; 161 | 162 | $WebSocket.prototype.onOpen = function onOpen(cb) { 163 | this.onOpenCallbacks.push(cb); 164 | return this; 165 | }; 166 | 167 | $WebSocket.prototype.onClose = function onClose(cb) { 168 | this.onCloseCallbacks.push(cb); 169 | return this; 170 | }; 171 | 172 | $WebSocket.prototype.onError = function onError(cb) { 173 | this.onErrorCallbacks.push(cb); 174 | return this; 175 | }; 176 | 177 | 178 | $WebSocket.prototype.onMessage = function onMessage(callback, options) { 179 | if (!isFunction(callback)) { 180 | throw new Error('Callback must be a function'); 181 | } 182 | 183 | if (options && isDefined(options.filter) && !isString(options.filter) && !(options.filter instanceof RegExp)) { 184 | throw new Error('Pattern must be a string or regular expression'); 185 | } 186 | 187 | this.onMessageCallbacks.push({ 188 | fn: callback, 189 | pattern: options ? options.filter : undefined, 190 | autoApply: options ? options.autoApply : true 191 | }); 192 | return this; 193 | }; 194 | 195 | $WebSocket.prototype._onOpenHandler = function _onOpenHandler(event) { 196 | this._reconnectAttempts = 0; 197 | this.notifyOpenCallbacks(event); 198 | this.fireQueue(); 199 | }; 200 | 201 | $WebSocket.prototype._onCloseHandler = function _onCloseHandler(event) { 202 | var self = this; 203 | if (self.useApplyAsync) { 204 | self.scope.$applyAsync(function() { 205 | self.notifyCloseCallbacks(event); 206 | }); 207 | } else { 208 | self.notifyCloseCallbacks(event); 209 | self.safeDigest(true); 210 | } 211 | if ((this.reconnectIfNotNormalClose && event.code !== this._normalCloseCode) || this._reconnectableStatusCodes.indexOf(event.code) > -1) { 212 | this.reconnect(); 213 | } 214 | }; 215 | 216 | $WebSocket.prototype._onErrorHandler = function _onErrorHandler(event) { 217 | var self = this; 218 | if (self.useApplyAsync) { 219 | self.scope.$applyAsync(function() { 220 | self.notifyErrorCallbacks(event); 221 | }); 222 | } else { 223 | self.notifyErrorCallbacks(event); 224 | self.safeDigest(true); 225 | } 226 | }; 227 | 228 | $WebSocket.prototype._onMessageHandler = function _onMessageHandler(message) { 229 | var pattern; 230 | var self = this; 231 | var currentCallback; 232 | for (var i = 0; i < self.onMessageCallbacks.length; i++) { 233 | currentCallback = self.onMessageCallbacks[i]; 234 | pattern = currentCallback.pattern; 235 | if (pattern) { 236 | if (isString(pattern) && message.data === pattern) { 237 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); 238 | } 239 | else if (pattern instanceof RegExp && pattern.exec(message.data)) { 240 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); 241 | } 242 | } 243 | else { 244 | applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message); 245 | } 246 | } 247 | 248 | function applyAsyncOrDigest(callback, autoApply, args) { 249 | args = arraySlice.call(arguments, 2); 250 | if (self.useApplyAsync) { 251 | self.scope.$applyAsync(function() { 252 | callback.apply(self, args); 253 | }); 254 | } else { 255 | callback.apply(self, args); 256 | self.safeDigest(autoApply); 257 | } 258 | } 259 | 260 | }; 261 | 262 | $WebSocket.prototype.close = function close(force) { 263 | if (force || !this.socket.bufferedAmount) { 264 | this.socket.close(); 265 | } 266 | return this; 267 | }; 268 | 269 | $WebSocket.prototype.send = function send(data) { 270 | var deferred = $q.defer(); 271 | var self = this; 272 | var promise = cancelableify(deferred.promise); 273 | 274 | if (self.readyState === self._readyStateConstants.RECONNECT_ABORTED) { 275 | deferred.reject('Socket connection has been closed'); 276 | } 277 | else { 278 | self.sendQueue.push({ 279 | message: data, 280 | deferred: deferred 281 | }); 282 | self.fireQueue(); 283 | } 284 | 285 | // Credit goes to @btford 286 | function cancelableify(promise) { 287 | promise.cancel = cancel; 288 | var then = promise.then; 289 | promise.then = function() { 290 | var newPromise = then.apply(this, arguments); 291 | return cancelableify(newPromise); 292 | }; 293 | return promise; 294 | } 295 | 296 | function cancel(reason) { 297 | self.sendQueue.splice(self.sendQueue.indexOf(data), 1); 298 | deferred.reject(reason); 299 | return self; 300 | } 301 | 302 | if ($websocketBackend.isMocked && $websocketBackend.isMocked() && 303 | $websocketBackend.isConnected(this.url)) { 304 | this._onMessageHandler($websocketBackend.mockSend()); 305 | } 306 | 307 | return promise; 308 | }; 309 | 310 | $WebSocket.prototype.reconnect = function reconnect() { 311 | this.close(); 312 | 313 | var backoffDelay = this._getBackoffDelay(++this._reconnectAttempts); 314 | 315 | var backoffDelaySeconds = backoffDelay / 1000; 316 | console.log('Reconnecting in ' + backoffDelaySeconds + ' seconds'); 317 | 318 | $timeout(angular.bind(this, this._connect), backoffDelay); 319 | 320 | return this; 321 | }; 322 | // Exponential Backoff Formula by Prof. Douglas Thain 323 | // http://dthain.blogspot.co.uk/2009/02/exponential-backoff-in-distributed.html 324 | $WebSocket.prototype._getBackoffDelay = function _getBackoffDelay(attempt) { 325 | var R = Math.random() + 1; 326 | var T = this.initialTimeout; 327 | var F = 2; 328 | var N = attempt; 329 | var M = this.maxTimeout; 330 | 331 | return Math.floor(Math.min(R * T * Math.pow(F, N), M)); 332 | }; 333 | 334 | $WebSocket.prototype._setInternalState = function _setInternalState(state) { 335 | if (Math.floor(state) !== state || state < 0 || state > 4) { 336 | throw new Error('state must be an integer between 0 and 4, got: ' + state); 337 | } 338 | 339 | // ie8 wat 340 | if (!objectDefineProperty) { 341 | this.readyState = state || this.socket.readyState; 342 | } 343 | this._internalConnectionState = state; 344 | 345 | 346 | forEach(this.sendQueue, function(pending) { 347 | pending.deferred.reject('Message cancelled due to closed socket connection'); 348 | }); 349 | }; 350 | 351 | // Read only .readyState 352 | if (objectDefineProperty) { 353 | objectDefineProperty($WebSocket.prototype, 'readyState', { 354 | get: function() { 355 | return this._internalConnectionState || this.socket.readyState; 356 | }, 357 | set: function() { 358 | throw new Error('The readyState property is read-only'); 359 | } 360 | }); 361 | } 362 | 363 | return function(url, protocols, options) { 364 | return new $WebSocket(url, protocols, options); 365 | }; 366 | } 367 | 368 | // $WebSocketBackendProvider.$inject = ['$log']; 369 | function $WebSocketBackendProvider($log) { 370 | this.create = function create(url, protocols, options) { 371 | var match = /wss?:\/\//.exec(url); 372 | 373 | if (!match) { 374 | throw new Error('Invalid url provided'); 375 | } 376 | 377 | if (options) { 378 | return new Socket(url, protocols, options); 379 | } 380 | 381 | if (protocols) { 382 | return new Socket(url, protocols); 383 | } 384 | 385 | return new Socket(url); 386 | }; 387 | 388 | this.createWebSocketBackend = function createWebSocketBackend(url, protocols) { 389 | $log.warn('Deprecated: Please use .create(url, protocols)'); 390 | return this.create(url, protocols); 391 | }; 392 | } 393 | 394 | angular.module('ngWebSocket', []) 395 | .factory('$websocket', ['$rootScope', '$q', '$timeout', '$websocketBackend', $WebSocketProvider]) 396 | .factory('WebSocket', ['$rootScope', '$q', '$timeout', 'WebsocketBackend', $WebSocketProvider]) 397 | .service('$websocketBackend', ['$log', $WebSocketBackendProvider]) 398 | .service('WebSocketBackend', ['$log', $WebSocketBackendProvider]); 399 | 400 | 401 | angular.module('angular-websocket', ['ngWebSocket']); 402 | 403 | export default angular.module('ngWebSocket'); 404 | -------------------------------------------------------------------------------- /test/angular-websocket.spec.js: -------------------------------------------------------------------------------- 1 | describe('angular-websocket', function() { 2 | 3 | describe('$websocketBackend', function() { 4 | var $window, $websocket, $websocketBackend, WSMock, localMocks = {}; 5 | 6 | beforeEach(module('ngWebSocket')); 7 | 8 | beforeEach(inject(function (_$window_, _$websocket_, _$websocketBackend_) { 9 | $window = _$window_; 10 | $websocket = _$websocket_; 11 | $websocketBackend = _$websocketBackend_; 12 | 13 | localMocks.sendMock = function() {}; 14 | localMocks.closeMock = function() {}; 15 | 16 | WSMock = function(url) { 17 | this.send = localMocks.sendMock; 18 | this.close = localMocks.closeMock; 19 | }; 20 | 21 | $window.WebSocket = WSMock; 22 | 23 | })); 24 | 25 | 26 | it('should complain if not given a valid url', function() { 27 | expect(function() { 28 | $websocketBackend.create('%foobar/baz'); 29 | }) 30 | .toThrowError('Invalid url provided'); 31 | }); 32 | 33 | }); 34 | 35 | 36 | describe('$websocket', function() { 37 | var $window, $websocket, $websocketBackend, WSMock, localMocks = {}; 38 | 39 | beforeEach(module('ngWebSocket', 'ngWebSocketMock')); 40 | 41 | beforeEach(inject(function (_$window_, _$websocket_, _$websocketBackend_) { 42 | $window = _$window_; 43 | $websocket = _$websocket_; 44 | $websocketBackend = _$websocketBackend_; 45 | 46 | localMocks.sendMock = function() {}; 47 | localMocks.closeMock = function() {}; 48 | 49 | WSMock = function(url) { 50 | this.send = localMocks.sendMock; 51 | this.close = localMocks.closeMock; 52 | }; 53 | 54 | $window.WebSocket = WSMock; 55 | })); 56 | 57 | afterEach(function() { 58 | $websocketBackend.verifyNoOutstandingRequest(); 59 | $websocketBackend.verifyNoOutstandingExpectation(); 60 | }); 61 | 62 | 63 | it('should accept a wss url', function() { 64 | var url = 'wss://foo/secure'; 65 | $websocketBackend.expectConnect(url); 66 | var ws = $websocket(url); 67 | expect(ws.ssl).toEqual(true); 68 | $websocketBackend.flush(); 69 | }); 70 | 71 | it('should accept protocols', function() { 72 | 73 | var url = 'ws://foo/secure'; 74 | var protocols1 = 'Swag-Protocol'; 75 | $websocketBackend.expectConnect(url, protocols1); 76 | var ws1 = $websocket(url, protocols1); 77 | expect(ws1.protocols).toEqual('Swag-Protocol'); 78 | 79 | var url2 = 'ws://foo/secure'; 80 | $websocketBackend.expectConnect(url2); 81 | var ws2 = $websocket(url2); 82 | expect(ws2.protocols).toEqual(undefined); 83 | 84 | var url3 = 'ws://foo/secure'; 85 | var protocols3 = ['Swag-Protocol', 'Sec-WebSocket-Protocol']; 86 | $websocketBackend.expectConnect(url3, protocols3); 87 | var ws3 = $websocket(url3, protocols3); 88 | expect(ws3.protocols).toEqual(['Swag-Protocol', 'Sec-WebSocket-Protocol']); 89 | 90 | $websocketBackend.flush(); 91 | }); 92 | 93 | 94 | it('should return an object containing a reference to the WebSocket instance', function() { 95 | var url = 'ws://reference'; 96 | $websocketBackend.expectConnect(url); 97 | expect(typeof $websocket(url).socket.send).toBe('function'); 98 | $websocketBackend.flush(); 99 | }); 100 | 101 | 102 | it('should have a separate sendQueue for each instance', function() { 103 | var url1 = 'ws://foo/one'; 104 | var url2 = 'ws://foo/two'; 105 | 106 | $websocketBackend.expectConnect(url1); 107 | var ws1 = $websocket(url1); 108 | 109 | $websocketBackend.expectConnect(url2); 110 | var ws2 = $websocket(url2); 111 | 112 | ws1.send('baz'); 113 | expect(ws1.sendQueue.length).toBe(1); 114 | expect(ws2.sendQueue.length).toBe(0); 115 | $websocketBackend.flush(); 116 | }); 117 | 118 | 119 | describe('._connect()', function() { 120 | var url, ws; 121 | 122 | beforeEach(function() { 123 | url = 'ws://foo/bar'; 124 | $websocketBackend.expectConnect(url); 125 | ws = $websocket(url); 126 | }); 127 | 128 | afterEach(function() { 129 | $websocketBackend.flush(); 130 | }); 131 | 132 | it('should attempt connecting to a socket if provided a valid URL', function() { 133 | ws.socket = null; 134 | ws._connect(); 135 | }); 136 | 137 | 138 | it('should not connect if a socket has a readyState of 1', function() { 139 | ws.socket.readyState = 1; 140 | ws._connect(); 141 | }); 142 | 143 | 144 | it('should force reconnect if force parameter is true', function() { 145 | ws.socket.readyState = 1; 146 | $websocketBackend.expectConnect(url); 147 | ws._connect(true); 148 | }); 149 | 150 | 151 | it('should attach handlers to socket event attributes', function() { 152 | expect(typeof ws.socket.onopen).toBe('function'); 153 | expect(typeof ws.socket.onmessage).toBe('function'); 154 | expect(typeof ws.socket.onerror).toBe('function'); 155 | expect(typeof ws.socket.onclose).toBe('function'); 156 | }); 157 | }); 158 | 159 | 160 | describe('.close()', function() { 161 | var url, ws; 162 | 163 | beforeEach(function() { 164 | url = 'ws://foo'; 165 | 166 | $websocketBackend.expectConnect(url); 167 | ws = $websocket(url); 168 | 169 | $websocketBackend.flush(); 170 | }); 171 | 172 | afterEach(function() { 173 | $websocketBackend.flush(); 174 | }); 175 | 176 | it('should call close on the underlying socket', function() { 177 | $websocketBackend.expectClose(); 178 | ws.close(); 179 | }); 180 | 181 | 182 | it('should not call close if the bufferedAmount is greater than 0', function() { 183 | ws.socket.bufferedAmount = 5; 184 | ws.close(); 185 | }); 186 | 187 | 188 | it('should accept a force param to close the socket even if bufferedAmount is greater than 0', function() { 189 | $websocketBackend.expectClose(); 190 | ws.socket.bufferedAmount = 5; 191 | ws.close(true); 192 | }); 193 | }); 194 | 195 | 196 | describe('.reconnect', function() { 197 | it('should call .close', function() { 198 | var url = 'ws://foo/onclose'; 199 | $websocketBackend.expectConnect(url); 200 | 201 | var ws = $websocket(url); 202 | $websocketBackend.expectClose(); 203 | ws.reconnect(); 204 | 205 | $websocketBackend.flush(); 206 | }); 207 | 208 | it('should call ._connect if the _getBackoffDelay is 0', inject(function($timeout) { 209 | var url = 'ws://foo/onclose'; 210 | $websocketBackend.expectConnect(url); 211 | 212 | var ws = $websocket(url); 213 | var spy = spyOn(ws, '_connect'); 214 | ws.reconnect(); 215 | $timeout.flush(); 216 | expect(spy).toHaveBeenCalled(); 217 | 218 | $websocketBackend.flush(); 219 | })); 220 | 221 | it('should not call ._connect if the _getBackoffDelay is not 0', inject(function($timeout) { 222 | var url = 'ws://foo/onclose'; 223 | $websocketBackend.expectConnect(url); 224 | 225 | var ws = $websocket(url); 226 | var spy = spyOn(ws, '_connect'); 227 | var spyBackoff = spyOn(ws, '_getBackoffDelay').and.callFake(function() { 228 | return 9001; 229 | }); 230 | ws.reconnect(); 231 | $timeout.flush(9000); 232 | expect(spy).not.toHaveBeenCalled(); 233 | $timeout.flush(1); 234 | expect(spy).toHaveBeenCalled(); 235 | 236 | $websocketBackend.flush(); 237 | })); 238 | }); 239 | 240 | 241 | describe('.safeDigest', function() { 242 | 243 | it('should force digest if force parameter is true', inject(function($rootScope) { 244 | var digest = spyOn($rootScope, '$digest'); 245 | 246 | var url = 'ws://foo/safeDigest'; 247 | $websocketBackend.expectConnect(url); 248 | 249 | var ws = $websocket(url); 250 | expect(digest).not.toHaveBeenCalled(); 251 | ws.safeDigest(true); 252 | expect(digest).toHaveBeenCalled(); 253 | 254 | $websocketBackend.flush(); 255 | })); 256 | 257 | it('should not digest if force parameter and $$phase are true', inject(function($rootScope) { 258 | var digest = spyOn($rootScope, '$digest'); 259 | 260 | var url = 'ws://foo/safeDigest'; 261 | $websocketBackend.expectConnect(url); 262 | 263 | var ws = $websocket(url); 264 | expect(digest).not.toHaveBeenCalled(); 265 | $rootScope.$$phase = true; 266 | ws.safeDigest(true); 267 | expect(digest).not.toHaveBeenCalled(); 268 | 269 | $websocketBackend.flush(); 270 | })); 271 | 272 | 273 | it('should digest scope parameter', inject(function($rootScope) { 274 | var isolateScope = $rootScope.$new(true); 275 | var digest = spyOn($rootScope, '$digest'); 276 | var childDigest = spyOn(isolateScope, '$digest'); 277 | 278 | var url = 'ws://foo/safeDigest'; 279 | $websocketBackend.expectConnect(url); 280 | 281 | var ws = $websocket(url); 282 | ws.scope = isolateScope; 283 | 284 | expect(digest).not.toHaveBeenCalled(); 285 | expect(childDigest).not.toHaveBeenCalled(); 286 | ws.safeDigest(true); 287 | 288 | expect(childDigest).toHaveBeenCalled(); 289 | expect(digest).not.toHaveBeenCalled(); 290 | 291 | $websocketBackend.flush(); 292 | })); 293 | 294 | }); 295 | 296 | describe('.bindToScope', function() { 297 | 298 | it('should not bind to scope if no parameters are provided', inject(function($rootScope) { 299 | // var digest = spyOn($rootScope, '$digest'); 300 | 301 | var url = 'ws://foo/bindToScope'; 302 | $websocketBackend.expectConnect(url); 303 | 304 | var ws = $websocket(url); 305 | expect(ws.scope).toBe($rootScope); 306 | ws.bindToScope(); 307 | expect(ws.scope).toBe($rootScope); 308 | 309 | $websocketBackend.flush(); 310 | })); 311 | 312 | it('should bind to scope if a scope is provided', inject(function($rootScope) { 313 | var isolateScope = $rootScope.$new(true); 314 | 315 | var url = 'ws://foo/bindToScope'; 316 | $websocketBackend.expectConnect(url); 317 | 318 | var ws = $websocket(url); 319 | expect(ws.scope).toBe($rootScope); 320 | ws.bindToScope(isolateScope); 321 | expect(ws.scope).toBe(isolateScope); 322 | 323 | $websocketBackend.flush(); 324 | })); 325 | 326 | it('should bind to rootScope if a scope is provided and is destroyed', inject(function($rootScope) { 327 | var isolateScope = $rootScope.$new(true); 328 | 329 | var url = 'ws://foo/bindToScope'; 330 | $websocketBackend.expectConnect(url); 331 | 332 | var ws = $websocket(url); 333 | ws.rootScopeFailover = true; 334 | 335 | expect(ws.scope).toBe($rootScope); 336 | ws.bindToScope(isolateScope); 337 | expect(ws.scope).toBe(isolateScope); 338 | 339 | isolateScope.$destroy(); 340 | 341 | expect(ws.scope).toBe($rootScope); 342 | 343 | $websocketBackend.flush(); 344 | })); 345 | 346 | 347 | }); 348 | 349 | describe('._onCloseHandler', function() { 350 | it('should call .reconnect if the CloseEvent contains a reconnectable status code and the reconnectIfNotNormalClose is false', function() { 351 | var url = 'ws://foo/onclose'; 352 | $websocketBackend.expectConnect(url); 353 | 354 | var ws = $websocket(url, {reconnectIfNotNormalClose: false}); 355 | var spy = spyOn(ws, 'reconnect'); 356 | ws._onCloseHandler({code: 4000}); 357 | expect(spy).toHaveBeenCalled(); 358 | 359 | $websocketBackend.flush(); 360 | }); 361 | 362 | it('should call .reconnect if the CloseEvent contains a reconnectable status code and the reconnectIfNotNormalClose is true', function() { 363 | var url = 'ws://foo/onclose'; 364 | $websocketBackend.expectConnect(url); 365 | 366 | var ws = $websocket(url, {reconnectIfNotNormalClose: true}); 367 | var spy = spyOn(ws, 'reconnect'); 368 | ws._onCloseHandler({code: 4000}); 369 | expect(spy).toHaveBeenCalled(); 370 | 371 | $websocketBackend.flush(); 372 | }); 373 | 374 | it('should not call .reconnect if the CloseEvent indicates an intentional close and the reconnectIfNotNormalClose flag is false', function() { 375 | var url = 'ws://foo/onclose'; 376 | $websocketBackend.expectConnect(url); 377 | 378 | var ws = $websocket(url, {reconnectIfNotNormalClose: false}); 379 | var spy = spyOn(ws, 'reconnect'); 380 | ws._onCloseHandler({code: 1000}); 381 | expect(spy).not.toHaveBeenCalled(); 382 | 383 | $websocketBackend.flush(); 384 | }); 385 | 386 | it('should not call .reconnect if the CloseEvent indicates an intentional close and the reconnectIfNotNormalClose flag is true', function() { 387 | var url = 'ws://foo/onclose'; 388 | $websocketBackend.expectConnect(url); 389 | 390 | var ws = $websocket(url, {reconnectIfNotNormalClose: true}); 391 | var spy = spyOn(ws, 'reconnect'); 392 | ws._onCloseHandler({code: 1000}); 393 | expect(spy).not.toHaveBeenCalled(); 394 | 395 | $websocketBackend.flush(); 396 | }); 397 | 398 | it('should not call .reconnect if the CloseEvent indicates a non-intentional close and the reconnectIfNotNormalClose flag is false', function() { 399 | var url = 'ws://foo/onclose'; 400 | $websocketBackend.expectConnect(url); 401 | 402 | var ws = $websocket(url, {reconnectIfNotNormalClose: false}); 403 | var spy = spyOn(ws, 'reconnect'); 404 | ws._onCloseHandler({code: 1001}); 405 | expect(spy).not.toHaveBeenCalled(); 406 | 407 | $websocketBackend.flush(); 408 | }); 409 | 410 | it('should call .reconnect if the CloseEvent indicates a non-intentional close and the reconnectIfNotNormalClose flag is true', function() { 411 | var url = 'ws://foo/onclose'; 412 | $websocketBackend.expectConnect(url); 413 | 414 | var ws = $websocket(url, {reconnectIfNotNormalClose: true}); 415 | var spy = spyOn(ws, 'reconnect'); 416 | ws._onCloseHandler({code: 1001}); 417 | expect(spy).toHaveBeenCalled(); 418 | 419 | $websocketBackend.flush(); 420 | }); 421 | }); 422 | 423 | 424 | describe('.onOpen()', function() { 425 | it('should add the passed in function to the onOpenCallbacks array', function() { 426 | var cb = function() {}; 427 | var url = 'ws://foo'; 428 | $websocketBackend.expectConnect(url); 429 | 430 | var ws = $websocket(url); 431 | ws.onOpen(cb); 432 | expect(ws.onOpenCallbacks[0]).toBe(cb); 433 | 434 | $websocketBackend.flush(); 435 | }); 436 | }); 437 | 438 | 439 | describe('.send()', function() { 440 | var url, ws; 441 | 442 | beforeEach(function() { 443 | url = 'ws://foo/bar'; 444 | $websocketBackend.expectConnect(url); 445 | ws = $websocket(url); 446 | }); 447 | 448 | afterEach(function() { 449 | $websocketBackend.flush(); 450 | }); 451 | 452 | 453 | it('should queue change if the "onopen" event has not yet occurred', function() { 454 | var data = {message: 'Send me'}; 455 | ws.send(data); 456 | expect(ws.sendQueue.length).toBe(1); 457 | expect(ws.sendQueue[0].message).toBe(data); 458 | }); 459 | 460 | 461 | it('should accept a string as data', function() { 462 | var data = 'I am a string'; 463 | ws.send(data); 464 | expect(ws.sendQueue[0].message).toBe(data); 465 | }); 466 | 467 | 468 | it('should call fireQueue immediately', function() { 469 | var spy = spyOn(ws, 'fireQueue'); 470 | ws.send('send me'); 471 | expect(spy).toHaveBeenCalled(); 472 | }); 473 | 474 | 475 | it('should return a promise', function() { 476 | expect(typeof ws.send('promise?').then).toBe('function'); 477 | }); 478 | 479 | 480 | it('should return a cancelable promise', function() { 481 | expect(typeof ws.send('promise?').cancel).toBe('function'); 482 | }); 483 | 484 | 485 | it('should return reject a cancelled send with reason if provided', inject(function($rootScope) { 486 | var reason = 'bad data'; 487 | var spy = jasmine.createSpy('reject'); 488 | ws.send('foo').then(null, spy).cancel(reason); 489 | $rootScope.$digest(); 490 | expect(spy).toHaveBeenCalledWith('bad data'); 491 | })); 492 | 493 | 494 | it('should remove the request from the queue when calling cancel', function() { 495 | ws.sendQueue = ['bar','baz']; 496 | var sent = ws.send('foo'); 497 | expect(ws.sendQueue[2].message).toBe('foo'); 498 | sent.cancel(); 499 | expect(ws.sendQueue.length).toBe(2); 500 | expect(ws.sendQueue.indexOf('foo')).toBe(-1); 501 | }); 502 | 503 | 504 | it('should reject the promise when readyState is 4', inject(function($rootScope) { 505 | var spy = jasmine.createSpy('reject'); 506 | ws._internalConnectionState = 4; 507 | ws.send('hello').then(null, spy); 508 | expect(ws.sendQueue.length).toBe(0); 509 | $rootScope.$digest(); 510 | expect(spy).toHaveBeenCalledWith('Socket connection has been closed'); 511 | })); 512 | }); 513 | 514 | 515 | describe('._setInternalState()', function() { 516 | 517 | it('should change the private _internalConnectionState property', function() { 518 | var ws = $websocket('ws://foo'); 519 | $websocketBackend.flush(); 520 | ws._setInternalState(4); 521 | expect(ws._internalConnectionState).toBe(4); 522 | }); 523 | 524 | 525 | it('should only allow integer values from 0-4', function() { 526 | var ws = $websocket('ws://foo'); 527 | $websocketBackend.flush(); 528 | ws._internalConnectionState = 4; 529 | expect(function() { 530 | ws._setInternalState(5); 531 | }).toThrowError('state must be an integer between 0 and 4, got: 5'); 532 | expect(ws._internalConnectionState).toBe(4); 533 | }); 534 | 535 | 536 | it('should cancel everything inside the sendQueue if the state is 4', inject(function($q) { 537 | var ws = $websocket('ws://foo'); 538 | $websocketBackend.flush(); 539 | var deferred = $q.defer(); 540 | var spy = spyOn(deferred, 'reject'); 541 | ws.sendQueue.push({ 542 | deferred: deferred, 543 | }); 544 | ws._setInternalState(4); 545 | expect(spy).toHaveBeenCalled(); 546 | })); 547 | }); 548 | 549 | 550 | describe('.onMessage()', function() { 551 | var fn, url, ws; 552 | 553 | beforeEach(function() { 554 | url = 'ws://foo'; 555 | fn = function() {}; 556 | $websocketBackend.expectConnect(url); 557 | ws = $websocket(url); 558 | }); 559 | 560 | 561 | afterEach(function() { 562 | $websocketBackend.flush(); 563 | }); 564 | 565 | 566 | it('should add the callback to a queue', function() { 567 | ws.onMessage(fn); 568 | expect(ws.onMessageCallbacks[0].fn).toBe(fn); 569 | }); 570 | 571 | 572 | it('should complain if not given a function', function() { 573 | expect(function() {ws.onMessage('lol');}).toThrowError('Callback must be a function'); 574 | }); 575 | 576 | 577 | it('should accept an options argument as the second argument', function() { 578 | ws.onMessage(fn, {filter: 'foo'}); 579 | }); 580 | 581 | 582 | it('should accept an optional RegEx pattern as the filter', function() { 583 | ws.onMessage(fn, {filter: /baz/}); 584 | }); 585 | 586 | 587 | it('should complain if the filter option is anything but RegEx or string', function() { 588 | expect(function() { 589 | ws.onMessage(fn, { filter: 5 }); 590 | }).toThrowError('Pattern must be a string or regular expression'); 591 | }); 592 | 593 | 594 | it('should set the autoApply property to true if undefined in options object', function() { 595 | ws.onMessage(angular.noop); 596 | expect(ws.onMessageCallbacks[0].autoApply).toBe(true); 597 | }); 598 | 599 | 600 | it('should set the autoApply property to false if specified in options object', function() { 601 | ws.onMessage(angular.noop, {autoApply: false}); 602 | expect(ws.onMessageCallbacks[0].autoApply).toBe(false); 603 | }); 604 | }); // end .onMessage() 605 | 606 | 607 | describe('._onMessageHandler()', function() { 608 | var fn, url, ws; 609 | 610 | beforeEach(function() { 611 | url = 'ws://foo'; 612 | fn = function() {}; 613 | $websocketBackend.expectConnect(url); 614 | ws = $websocket(url); 615 | }); 616 | 617 | afterEach(function() { 618 | $websocketBackend.flush(); 619 | }); 620 | 621 | 622 | it('should call callback if message matches filter pattern', function() { 623 | var spy = jasmine.createSpy('onResolve'); 624 | ws.onMessageCallbacks.push({fn: spy, pattern: /baz[0-9]{2}/}); 625 | ws._onMessageHandler({data: 'bar'}); 626 | expect(spy).not.toHaveBeenCalled(); 627 | ws._onMessageHandler({data: 'baz21'}); 628 | expect(spy).toHaveBeenCalled(); 629 | }); 630 | 631 | 632 | it('should only call callback if message matches filter string exactly', function() { 633 | var spy = jasmine.createSpy('onResolve'); 634 | ws.onMessageCallbacks.push({fn: spy, pattern: 'foo'}); 635 | ws._onMessageHandler({data: 'bar'}); 636 | expect(spy).not.toHaveBeenCalled(); 637 | ws._onMessageHandler({data: 'foo'}); 638 | expect(spy).toHaveBeenCalled(); 639 | }); 640 | 641 | 642 | it('should call $rootScope.$digest() if autoApply is set to true', inject(function($rootScope) { 643 | var digest = spyOn($rootScope, '$digest'); 644 | ws.onMessageCallbacks.push({fn: angular.noop, autoApply: true}); 645 | ws._onMessageHandler({data: 'Hello'}); 646 | expect(digest).toHaveBeenCalled(); 647 | })); 648 | 649 | 650 | it('should not call $rootScope.$digest() if autoApply is set to false', inject(function($rootScope) { 651 | var digest = spyOn($rootScope, '$digest'); 652 | ws.onMessageCallbacks.push({fn: angular.noop, autoApply: false}); 653 | ws._onMessageHandler({data: 'Hello'}); 654 | expect(digest).not.toHaveBeenCalled(); 655 | })); 656 | 657 | 658 | it('should not call $rootScope.$digest() if a digest is already in progress', inject(function($rootScope){ 659 | $rootScope.$$phase = '$digest'; 660 | var digest = spyOn($rootScope, '$digest'); 661 | ws.onMessageCallbacks.push({fn: angular.noop, autoApply: true}); 662 | ws._onMessageHandler({data: 'Hello'}); 663 | expect(digest).not.toHaveBeenCalled(); 664 | })); 665 | }); // end ._onMessageHandler() 666 | 667 | 668 | describe('._onOpenHandler()', function() { 669 | var url, ws; 670 | 671 | beforeEach(function() { 672 | url = 'ws://foo'; 673 | $websocketBackend.expectConnect(url); 674 | ws = $websocket(url); 675 | $websocketBackend.flush(); 676 | }); 677 | 678 | it('should call fireQueue to flush any queued send() calls', function() { 679 | var spy = spyOn(ws, 'fireQueue'); 680 | ws._onOpenHandler.call(ws); 681 | expect(spy).toHaveBeenCalled(); 682 | }); 683 | 684 | 685 | it('should call the passed-in function when a socket first connects', function() { 686 | var spy = jasmine.createSpy('callback'); 687 | ws.onOpenCallbacks.push(spy); 688 | ws._onOpenHandler.call(ws); 689 | expect(spy).toHaveBeenCalled(); 690 | }); 691 | 692 | 693 | it('should call the passed-in function when a socket re-connects', function() { 694 | var spy = jasmine.createSpy('callback'); 695 | ws.onOpenCallbacks.push(spy); 696 | ws._onOpenHandler.call(ws); 697 | ws._onOpenHandler.call(ws); 698 | expect(spy.calls.count()).toBe(2); 699 | }); 700 | 701 | 702 | it('should call multiple callbacks when connecting', function() { 703 | var spy1 = jasmine.createSpy('callback1'); 704 | var spy2 = jasmine.createSpy('callback2'); 705 | ws.onOpenCallbacks.push(spy1); 706 | ws.onOpenCallbacks.push(spy2); 707 | ws._onOpenHandler.call(ws); 708 | expect(spy1).toHaveBeenCalled(); 709 | expect(spy2).toHaveBeenCalled(); 710 | }); 711 | }); // end ._onOpenHandler() 712 | 713 | 714 | describe('.fireQueue()', function() { 715 | var ws; 716 | 717 | beforeEach(function() { 718 | var url = 'ws://foo/bar'; 719 | $websocketBackend.expectConnect(url); 720 | ws = $websocket(url); 721 | $websocketBackend.flush(); 722 | }); 723 | 724 | 725 | it('should not affect the queue if the readyState is not 1', function() { 726 | var data = {message: 'Hello', deferred: {resolve: angular.noop}}; 727 | ws.socket.readyState = 0; 728 | ws.send(data); 729 | expect(ws.sendQueue.length).toBe(1); 730 | ws.fireQueue(); 731 | expect(ws.sendQueue.length).toBe(1); 732 | }); 733 | 734 | 735 | it('should call send for every item in the queue if readyState is 1', function() { 736 | var data = {message: 'Hello', deferred: {resolve: angular.noop}}; 737 | var stringified = JSON.stringify(data); 738 | $websocketBackend.expectSend(stringified); 739 | ws.sendQueue.unshift(data); 740 | $websocketBackend.expectSend(stringified); 741 | ws.sendQueue.unshift(data); 742 | $websocketBackend.expectSend(stringified); 743 | ws.sendQueue.unshift(data); 744 | ws.socket.readyState = 1; 745 | 746 | expect(ws.sendQueue.length).toBe(3); 747 | ws.fireQueue(); 748 | expect(ws.sendQueue.length).toBe(0); 749 | $websocketBackend.flush(); 750 | }); 751 | 752 | 753 | it('should stringify an object when sending to socket', function() { 754 | var data = {message: 'Send me', deferred: {resolve: angular.noop}}; 755 | var stringified = JSON.stringify(data); 756 | ws.socket.readyState = 1; 757 | $websocketBackend.expectSend(stringified); 758 | ws.sendQueue.unshift(data); 759 | ws.fireQueue(); 760 | $websocketBackend.flush(); 761 | }); 762 | 763 | 764 | it('should resolve the deferred when it has been sent to the underlying socket', inject(function($q, $rootScope) { 765 | var message = 'Send me'; 766 | var deferred = $q.defer(); 767 | var spy = jasmine.createSpy('resolve'); 768 | deferred.promise.then(spy); 769 | var data = {deferred: deferred, message: message}; 770 | ws.socket.readyState = 1; 771 | $websocketBackend.expectSend(message); 772 | ws.sendQueue.unshift(data); 773 | ws.fireQueue(); 774 | $rootScope.$digest(); 775 | $websocketBackend.flush(); 776 | expect(spy).toHaveBeenCalled(); 777 | })); 778 | }); // end .fireQueue() 779 | 780 | 781 | describe('.readyState', function() { 782 | var url, ws; 783 | 784 | beforeEach(function() { 785 | url = 'ws://foo'; 786 | $websocketBackend.expectConnect(url); 787 | ws = $websocket(url); 788 | $websocketBackend.flush(); 789 | }); 790 | 791 | 792 | it('should provide the readyState of the underlying socket', function() { 793 | ws.socket.readyState = 1; 794 | expect(ws.readyState).toBe(1); 795 | }); 796 | 797 | 798 | it('should complain if I try to set the readyState', function() { 799 | expect(function() { 800 | ws.readyState = 5; 801 | }).toThrowError('The readyState property is read-only'); 802 | }); 803 | 804 | 805 | it('should return a proprietary readyState if lib is in a special state', function() { 806 | ws.socket.readyState = 1; 807 | ws._internalConnectionState = 5; 808 | expect(ws.readyState).toBe(5); 809 | }); 810 | 811 | }); // end .readyState 812 | 813 | 814 | describe('._readyStateConstants', function() { 815 | var url, ws; 816 | 817 | beforeEach(function() { 818 | url = 'ws://foo'; 819 | $websocketBackend.expectConnect(url); 820 | ws = $websocket(url); 821 | $websocketBackend.flush(); 822 | }); 823 | 824 | it('should contain the basic readyState constants for WebSocket', function() { 825 | var constants = ws._readyStateConstants; 826 | expect(constants.CONNECTING).toBe(0); 827 | expect(constants.OPEN).toBe(1); 828 | expect(constants.CLOSING).toBe(2); 829 | expect(constants.CLOSED).toBe(3); 830 | }); 831 | 832 | 833 | it('should provide custom constants to represent lib state', function() { 834 | var constants = ws._readyStateConstants; 835 | expect(constants.RECONNECT_ABORTED).toBe(4); 836 | }); 837 | 838 | 839 | it('should ignore attempts at changing constants', function() { 840 | ws._readyStateConstants.CONNECTING = 'foo'; 841 | expect(ws._readyStateConstants.CONNECTING).toBe(0); 842 | }); 843 | }); // end ._readyStateConstants 844 | 845 | 846 | describe('._reconnectableStatusCodes', function() { 847 | it('should contain status codes that warrant re-establishing a connection', function() { 848 | var url = 'ws://foo'; 849 | $websocketBackend.expectConnect(url); 850 | var ws = $websocket(url); 851 | expect(ws._reconnectableStatusCodes.length).toBe(1); 852 | expect(ws._reconnectableStatusCodes).toEqual([4000]); 853 | $websocketBackend.flush(); 854 | }); 855 | 856 | 857 | }); 858 | 859 | 860 | // alias 861 | xdescribe('WebSocket', function() { 862 | it('should alias $websocket', inject(function(WebSocket) { 863 | expect(WebSocket).toBe($websocket); 864 | })); 865 | }); 866 | 867 | 868 | // alias 869 | xdescribe('WebSocketBackend', function() { 870 | it('should alias $websocketBackend', inject(function(WebSocketBackend) { 871 | expect(WebSocketBackend).toBe($websocketBackend); 872 | })); 873 | }); 874 | 875 | 876 | describe('.onError()', function() { 877 | 878 | it('should add the passed in function to the onErrorCallbacks array', function() { 879 | var cb = function() {}; 880 | var url = 'ws://foo'; 881 | $websocketBackend.expectConnect(url); 882 | var ws = $websocket(url); 883 | ws.onError(cb); 884 | expect(ws.onErrorCallbacks[0]).toBe(cb); 885 | $websocketBackend.flush(); 886 | }); 887 | 888 | }); // end .onError() 889 | 890 | 891 | describe('._onErrorHandler()', function() { 892 | var url, ws; 893 | 894 | beforeEach(function() { 895 | url = 'ws://foo'; 896 | $websocketBackend.expectConnect(url); 897 | ws = $websocket(url); 898 | $websocketBackend.flush(); 899 | }); 900 | 901 | it('should call the passed-in function when an error occurs', function() { 902 | var spy = jasmine.createSpy('callback'); 903 | ws.onErrorCallbacks.push(spy); 904 | ws._onErrorHandler.call(ws, new Error()); 905 | expect(spy).toHaveBeenCalled(); 906 | }); 907 | 908 | 909 | it('should call multiple callbacks when connecting', function() { 910 | var spy1 = jasmine.createSpy('callback1'); 911 | var spy2 = jasmine.createSpy('callback2'); 912 | ws.onErrorCallbacks.push(spy1); 913 | ws.onErrorCallbacks.push(spy2); 914 | ws._onErrorHandler.call(ws); 915 | expect(spy1).toHaveBeenCalled(); 916 | expect(spy2).toHaveBeenCalled(); 917 | }); 918 | 919 | }); // end ._onErrorHandler() 920 | 921 | 922 | }); // end $websocketBackend 923 | }); // end $websocket 924 | --------------------------------------------------------------------------------