├── .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 |
4 |
5 |
6 |
7 |
8 | # Angular Websocket [](https://angularclass.com/slack-join) [](https://gitter.im/AngularClass/angular-websocket?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) [](https://www.omniref.com/github/gdi2290/angular-websocket)
9 |
10 | [](https://travis-ci.org/gdi2290/angular-websocket)
11 | [](https://github.com/gdi2290/angular-websocket)
12 | [](https://www.npmjs.com/package/angular-websocket)
13 | [](https://david-dm.org/gdi2290/angular-websocket)
14 | [](https://david-dm.org/gdi2290/angular-websocket#info=devDependencies)
15 | [](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 |
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 | [](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 |
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 |
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 |
--------------------------------------------------------------------------------