angular-websocket.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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. Object.defineProperty(exports, "__esModule", {
  16. value: true
  17. });
  18. var _angular2 = _interopRequireDefault(_angular);
  19. function _interopRequireDefault(obj) {
  20. return obj && obj.__esModule ? obj : {
  21. default: obj
  22. };
  23. }
  24. var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
  25. return typeof obj;
  26. } : function (obj) {
  27. return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj;
  28. };
  29. var Socket;
  30. if (typeof window === 'undefined') {
  31. try {
  32. Socket = ws.Client || ws.client || ws;
  33. } catch (e) {}
  34. }
  35. // Browser
  36. Socket = Socket || window.WebSocket || window.MozWebSocket;
  37. var noop = _angular2.default.noop;
  38. var objectFreeze = Object.freeze ? Object.freeze : noop;
  39. var objectDefineProperty = Object.defineProperty;
  40. var isString = _angular2.default.isString;
  41. var isFunction = _angular2.default.isFunction;
  42. var isDefined = _angular2.default.isDefined;
  43. var isObject = _angular2.default.isObject;
  44. var isArray = _angular2.default.isArray;
  45. var forEach = _angular2.default.forEach;
  46. var arraySlice = Array.prototype.slice;
  47. // ie8 wat
  48. if (!Array.prototype.indexOf) {
  49. Array.prototype.indexOf = function (elt /*, from*/) {
  50. var len = this.length >>> 0;
  51. var from = Number(arguments[1]) || 0;
  52. from = from < 0 ? Math.ceil(from) : Math.floor(from);
  53. if (from < 0) {
  54. from += len;
  55. }
  56. for (; from < len; from++) {
  57. if (from in this && this[from] === elt) {
  58. return from;
  59. }
  60. }
  61. return -1;
  62. };
  63. }
  64. // $WebSocketProvider.$inject = ['$rootScope', '$q', '$timeout', '$websocketBackend'];
  65. function $WebSocketProvider($rootScope, $q, $timeout, $websocketBackend) {
  66. function $WebSocket(url, protocols, options) {
  67. if (!options && isObject(protocols) && !isArray(protocols)) {
  68. options = protocols;
  69. protocols = undefined;
  70. }
  71. this.protocols = protocols;
  72. this.url = url || 'Missing URL';
  73. this.ssl = /(wss)/i.test(this.url);
  74. // this.binaryType = '';
  75. // this.extensions = '';
  76. // this.bufferedAmount = 0;
  77. // this.trasnmitting = false;
  78. // this.buffer = [];
  79. // TODO: refactor options to use isDefined
  80. this.scope = options && options.scope || $rootScope;
  81. this.rootScopeFailover = options && options.rootScopeFailover && true;
  82. this.useApplyAsync = options && options.useApplyAsync || false;
  83. this.initialTimeout = options && options.initialTimeout || 500; // 500ms
  84. this.maxTimeout = options && options.maxTimeout || 5 * 60 * 1000; // 5 minutes
  85. this.reconnectIfNotNormalClose = options && options.reconnectIfNotNormalClose || false;
  86. this.binaryType = options && options.binaryType || 'blob';
  87. this._reconnectAttempts = 0;
  88. this.sendQueue = [];
  89. this.onOpenCallbacks = [];
  90. this.onMessageCallbacks = [];
  91. this.onErrorCallbacks = [];
  92. this.onCloseCallbacks = [];
  93. objectFreeze(this._readyStateConstants);
  94. if (url) {
  95. this._connect();
  96. } else {
  97. this._setInternalState(0);
  98. }
  99. }
  100. $WebSocket.prototype._readyStateConstants = {
  101. 'CONNECTING': 0,
  102. 'OPEN': 1,
  103. 'CLOSING': 2,
  104. 'CLOSED': 3,
  105. 'RECONNECT_ABORTED': 4
  106. };
  107. $WebSocket.prototype._normalCloseCode = 1000;
  108. $WebSocket.prototype._reconnectableStatusCodes = [4000];
  109. $WebSocket.prototype.safeDigest = function safeDigest(autoApply) {
  110. if (autoApply && !this.scope.$$phase) {
  111. this.scope.$digest();
  112. }
  113. };
  114. $WebSocket.prototype.bindToScope = function bindToScope(scope) {
  115. var self = this;
  116. if (scope) {
  117. this.scope = scope;
  118. if (this.rootScopeFailover) {
  119. this.scope.$on('$destroy', function () {
  120. self.scope = $rootScope;
  121. });
  122. }
  123. }
  124. return self;
  125. };
  126. $WebSocket.prototype._connect = function _connect(force) {
  127. if (force || !this.socket || this.socket.readyState !== this._readyStateConstants.OPEN) {
  128. this.socket = $websocketBackend.create(this.url, this.protocols);
  129. this.socket.onmessage = _angular2.default.bind(this, this._onMessageHandler);
  130. this.socket.onopen = _angular2.default.bind(this, this._onOpenHandler);
  131. this.socket.onerror = _angular2.default.bind(this, this._onErrorHandler);
  132. this.socket.onclose = _angular2.default.bind(this, this._onCloseHandler);
  133. this.socket.binaryType = this.binaryType;
  134. }
  135. };
  136. $WebSocket.prototype.fireQueue = function fireQueue() {
  137. while (this.sendQueue.length && this.socket.readyState === this._readyStateConstants.OPEN) {
  138. var data = this.sendQueue.shift();
  139. this.socket.send(isString(data.message) || this.binaryType != 'blob' ? data.message : JSON.stringify(data.message));
  140. data.deferred.resolve();
  141. }
  142. };
  143. $WebSocket.prototype.notifyOpenCallbacks = function notifyOpenCallbacks(event) {
  144. for (var i = 0; i < this.onOpenCallbacks.length; i++) {
  145. this.onOpenCallbacks[i].call(this, event);
  146. }
  147. };
  148. $WebSocket.prototype.notifyCloseCallbacks = function notifyCloseCallbacks(event) {
  149. for (var i = 0; i < this.onCloseCallbacks.length; i++) {
  150. this.onCloseCallbacks[i].call(this, event);
  151. }
  152. };
  153. $WebSocket.prototype.notifyErrorCallbacks = function notifyErrorCallbacks(event) {
  154. for (var i = 0; i < this.onErrorCallbacks.length; i++) {
  155. this.onErrorCallbacks[i].call(this, event);
  156. }
  157. };
  158. $WebSocket.prototype.onOpen = function onOpen(cb) {
  159. this.onOpenCallbacks.push(cb);
  160. return this;
  161. };
  162. $WebSocket.prototype.onClose = function onClose(cb) {
  163. this.onCloseCallbacks.push(cb);
  164. return this;
  165. };
  166. $WebSocket.prototype.onError = function onError(cb) {
  167. this.onErrorCallbacks.push(cb);
  168. return this;
  169. };
  170. $WebSocket.prototype.onMessage = function onMessage(callback, options) {
  171. if (!isFunction(callback)) {
  172. throw new Error('Callback must be a function');
  173. }
  174. if (options && isDefined(options.filter) && !isString(options.filter) && !(options.filter instanceof RegExp)) {
  175. throw new Error('Pattern must be a string or regular expression');
  176. }
  177. this.onMessageCallbacks.push({
  178. fn: callback,
  179. pattern: options ? options.filter : undefined,
  180. autoApply: options ? options.autoApply : true
  181. });
  182. return this;
  183. };
  184. $WebSocket.prototype._onOpenHandler = function _onOpenHandler(event) {
  185. this._reconnectAttempts = 0;
  186. this.notifyOpenCallbacks(event);
  187. this.fireQueue();
  188. };
  189. $WebSocket.prototype._onCloseHandler = function _onCloseHandler(event) {
  190. var self = this;
  191. if (self.useApplyAsync) {
  192. self.scope.$applyAsync(function () {
  193. self.notifyCloseCallbacks(event);
  194. });
  195. } else {
  196. self.notifyCloseCallbacks(event);
  197. self.safeDigest(true);
  198. }
  199. if (this.reconnectIfNotNormalClose && event.code !== this._normalCloseCode || this._reconnectableStatusCodes.indexOf(event.code) > -1) {
  200. this.reconnect();
  201. }
  202. };
  203. $WebSocket.prototype._onErrorHandler = function _onErrorHandler(event) {
  204. var self = this;
  205. if (self.useApplyAsync) {
  206. self.scope.$applyAsync(function () {
  207. self.notifyErrorCallbacks(event);
  208. });
  209. } else {
  210. self.notifyErrorCallbacks(event);
  211. self.safeDigest(true);
  212. }
  213. };
  214. $WebSocket.prototype._onMessageHandler = function _onMessageHandler(message) {
  215. var pattern;
  216. var self = this;
  217. var currentCallback;
  218. for (var i = 0; i < self.onMessageCallbacks.length; i++) {
  219. currentCallback = self.onMessageCallbacks[i];
  220. pattern = currentCallback.pattern;
  221. if (pattern) {
  222. if (isString(pattern) && message.data === pattern) {
  223. applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message);
  224. } else if (pattern instanceof RegExp && pattern.exec(message.data)) {
  225. applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message);
  226. }
  227. } else {
  228. applyAsyncOrDigest(currentCallback.fn, currentCallback.autoApply, message);
  229. }
  230. }
  231. function applyAsyncOrDigest(callback, autoApply, args) {
  232. args = arraySlice.call(arguments, 2);
  233. if (self.useApplyAsync) {
  234. self.scope.$applyAsync(function () {
  235. callback.apply(self, args);
  236. });
  237. } else {
  238. callback.apply(self, args);
  239. self.safeDigest(autoApply);
  240. }
  241. }
  242. };
  243. $WebSocket.prototype.close = function close(force) {
  244. if (force || !this.socket.bufferedAmount) {
  245. this.socket.close();
  246. }
  247. return this;
  248. };
  249. $WebSocket.prototype.send = function send(data) {
  250. var deferred = $q.defer();
  251. var self = this;
  252. var promise = cancelableify(deferred.promise);
  253. if (self.readyState === self._readyStateConstants.RECONNECT_ABORTED) {
  254. deferred.reject('Socket connection has been closed');
  255. } else {
  256. self.sendQueue.push({
  257. message: data,
  258. deferred: deferred
  259. });
  260. self.fireQueue();
  261. }
  262. // Credit goes to @btford
  263. function cancelableify(promise) {
  264. promise.cancel = cancel;
  265. var then = promise.then;
  266. promise.then = function () {
  267. var newPromise = then.apply(this, arguments);
  268. return cancelableify(newPromise);
  269. };
  270. return promise;
  271. }
  272. function cancel(reason) {
  273. self.sendQueue.splice(self.sendQueue.indexOf(data), 1);
  274. deferred.reject(reason);
  275. return self;
  276. }
  277. if ($websocketBackend.isMocked && $websocketBackend.isMocked() && $websocketBackend.isConnected(this.url)) {
  278. this._onMessageHandler($websocketBackend.mockSend());
  279. }
  280. return promise;
  281. };
  282. $WebSocket.prototype.reconnect = function reconnect() {
  283. this.close();
  284. var backoffDelay = this._getBackoffDelay(++this._reconnectAttempts);
  285. var backoffDelaySeconds = backoffDelay / 1000;
  286. console.log('Reconnecting in ' + backoffDelaySeconds + ' seconds');
  287. $timeout(_angular2.default.bind(this, this._connect), backoffDelay);
  288. return this;
  289. };
  290. // Exponential Backoff Formula by Prof. Douglas Thain
  291. // http://dthain.blogspot.co.uk/2009/02/exponential-backoff-in-distributed.html
  292. $WebSocket.prototype._getBackoffDelay = function _getBackoffDelay(attempt) {
  293. var R = Math.random() + 1;
  294. var T = this.initialTimeout;
  295. var F = 2;
  296. var N = attempt;
  297. var M = this.maxTimeout;
  298. return Math.floor(Math.min(R * T * Math.pow(F, N), M));
  299. };
  300. $WebSocket.prototype._setInternalState = function _setInternalState(state) {
  301. if (Math.floor(state) !== state || state < 0 || state > 4) {
  302. throw new Error('state must be an integer between 0 and 4, got: ' + state);
  303. }
  304. // ie8 wat
  305. if (!objectDefineProperty) {
  306. this.readyState = state || this.socket.readyState;
  307. }
  308. this._internalConnectionState = state;
  309. forEach(this.sendQueue, function (pending) {
  310. pending.deferred.reject('Message cancelled due to closed socket connection');
  311. });
  312. };
  313. // Read only .readyState
  314. if (objectDefineProperty) {
  315. objectDefineProperty($WebSocket.prototype, 'readyState', {
  316. get: function get() {
  317. return this._internalConnectionState || this.socket.readyState;
  318. },
  319. set: function set() {
  320. throw new Error('The readyState property is read-only');
  321. }
  322. });
  323. }
  324. return function (url, protocols, options) {
  325. return new $WebSocket(url, protocols, options);
  326. };
  327. }
  328. // $WebSocketBackendProvider.$inject = ['$log'];
  329. function $WebSocketBackendProvider($log) {
  330. this.create = function create(url, protocols) {
  331. var match = /wss?:\/\//.exec(url);
  332. if (!match) {
  333. throw new Error('Invalid url provided');
  334. }
  335. if (protocols) {
  336. return new Socket(url, protocols);
  337. }
  338. return new Socket(url);
  339. };
  340. this.createWebSocketBackend = function createWebSocketBackend(url, protocols) {
  341. $log.warn('Deprecated: Please use .create(url, protocols)');
  342. return this.create(url, protocols);
  343. };
  344. }
  345. _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]);
  346. _angular2.default.module('angular-websocket', ['ngWebSocket']);
  347. exports.default = _angular2.default.module('ngWebSocket');
  348. module.exports = exports['default'];
  349. });