blob: b2317cdd7b3a6fbb67503993d269e95b25776d74 [file] [log] [blame]
Ed Tanous904063f2017-03-02 16:48:24 -08001(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 exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object' && typeof require === 'function') {
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});