File: source/socket-channel.js

  1. /**
  2. * The list of <a href="#method_joinRoom"><code>joinRoom()</code> method</a> socket connection failure states.
  3. * @attribute SOCKET_ERROR
  4. * @param {Number} CONNECTION_FAILED <small>Value <code>0</code></small>
  5. * The value of the failure state when <code>joinRoom()</code> socket connection failed to establish with
  6. * the Signaling server at the first attempt.
  7. * @param {Number} RECONNECTION_FAILED <small>Value <code>-1</code></small>
  8. * The value of the failure state when <code>joinRoom()</code> socket connection failed to establish
  9. * the Signaling server after the first attempt.
  10. * @param {Number} CONNECTION_ABORTED <small>Value <code>-2</code></small>
  11. * The value of the failure state when <code>joinRoom()</code> socket connection will not attempt
  12. * to reconnect after the failure of the first attempt in <code>CONNECTION_FAILED</code> as there
  13. * are no more ports or transports to attempt for reconnection.
  14. * @param {Number} RECONNECTION_ABORTED <small>Value <code>-3</code></small>
  15. * The value of the failure state when <code>joinRoom()</code> socket connection will not attempt
  16. * to reconnect after the failure of several attempts in <code>RECONNECTION_FAILED</code> as there
  17. * are no more ports or transports to attempt for reconnection.
  18. * @param {Number} RECONNECTION_ATTEMPT <small>Value <code>-4</code></small>
  19. * The value of the failure state when <code>joinRoom()</code> socket connection is attempting
  20. * to reconnect with a new port or transport after the failure of attempts in
  21. * <code>CONNECTION_FAILED</code> or <code>RECONNECTED_FAILED</code>.
  22. * @type JSON
  23. * @readOnly
  24. * @for Skylink
  25. * @since 0.5.6
  26. */
  27. Skylink.prototype.SOCKET_ERROR = {
  28. CONNECTION_FAILED: 0,
  29. RECONNECTION_FAILED: -1,
  30. CONNECTION_ABORTED: -2,
  31. RECONNECTION_ABORTED: -3,
  32. RECONNECTION_ATTEMPT: -4
  33. };
  34.  
  35. /**
  36. * The list of <a href="#method_joinRoom"><code>joinRoom()</code> method</a> socket connection reconnection states.
  37. * @attribute SOCKET_FALLBACK
  38. * @param {String} NON_FALLBACK <small>Value <code>"nonfallback"</code></small>
  39. * The value of the reconnection state when <code>joinRoom()</code> socket connection is at its initial state
  40. * without transitioning to any new socket port or transports yet.
  41. * @param {String} FALLBACK_PORT <small>Value <code>"fallbackPortNonSSL"</code></small>
  42. * The value of the reconnection state when <code>joinRoom()</code> socket connection is reconnecting with
  43. * another new HTTP port using WebSocket transports to attempt to establish connection with Signaling server.
  44. * @param {String} FALLBACK_PORT_SSL <small>Value <code>"fallbackPortSSL"</code></small>
  45. * The value of the reconnection state when <code>joinRoom()</code> socket connection is reconnecting with
  46. * another new HTTPS port using WebSocket transports to attempt to establish connection with Signaling server.
  47. * @param {String} LONG_POLLING <small>Value <code>"fallbackLongPollingNonSSL"</code></small>
  48. * The value of the reconnection state when <code>joinRoom()</code> socket connection is reconnecting with
  49. * another new HTTP port using Polling transports to attempt to establish connection with Signaling server.
  50. * @param {String} LONG_POLLING <small>Value <code>"fallbackLongPollingSSL"</code></small>
  51. * The value of the reconnection state when <code>joinRoom()</code> socket connection is reconnecting with
  52. * another new HTTPS port using Polling transports to attempt to establish connection with Signaling server.
  53. * @type JSON
  54. * @readOnly
  55. * @for Skylink
  56. * @since 0.5.6
  57. */
  58. Skylink.prototype.SOCKET_FALLBACK = {
  59. NON_FALLBACK: 'nonfallback',
  60. FALLBACK_PORT: 'fallbackPortNonSSL',
  61. FALLBACK_SSL_PORT: 'fallbackPortSSL',
  62. LONG_POLLING: 'fallbackLongPollingNonSSL',
  63. LONG_POLLING_SSL: 'fallbackLongPollingSSL'
  64. };
  65.  
  66. /**
  67. * Stores the current socket connection information.
  68. * @attribute _socketSession
  69. * @type JSON
  70. * @private
  71. * @for Skylink
  72. * @since 0.6.13
  73. */
  74. Skylink.prototype._socketSession = {};
  75.  
  76. /**
  77. * Stores the queued socket messages.
  78. * This is to prevent too many sent over less than a second interval that might cause dropped messages
  79. * or jams to the Signaling connection.
  80. * @attribute _socketMessageQueue
  81. * @type Array
  82. * @private
  83. * @for Skylink
  84. * @since 0.5.8
  85. */
  86. Skylink.prototype._socketMessageQueue = [];
  87.  
  88. /**
  89. * Stores the <code>setTimeout</code> to sent queued socket messages.
  90. * @attribute _socketMessageTimeout
  91. * @type Object
  92. * @private
  93. * @for Skylink
  94. * @since 0.5.8
  95. */
  96. Skylink.prototype._socketMessageTimeout = null;
  97.  
  98. /**
  99. * Stores the list of socket ports to use to connect to the Signaling.
  100. * These ports are defined by default which is commonly used currently by the Signaling.
  101. * Should re-evaluate this sometime.
  102. * @attribute _socketPorts
  103. * @param {Array} http: The list of HTTP socket ports.
  104. * @param {Array} https: The list of HTTPS socket ports.
  105. * @type JSON
  106. * @private
  107. * @for Skylink
  108. * @since 0.5.8
  109. */
  110. Skylink.prototype._socketPorts = {
  111. 'http:': [80, 3000],
  112. 'https:': [443, 3443]
  113. };
  114.  
  115. /**
  116. * Stores the flag that indicates if socket connection to the Signaling has opened.
  117. * @attribute _channelOpen
  118. * @type Boolean
  119. * @private
  120. * @for Skylink
  121. * @since 0.5.2
  122. */
  123. Skylink.prototype._channelOpen = false;
  124.  
  125. /**
  126. * Stores the Signaling server url.
  127. * @attribute _signalingServer
  128. * @type String
  129. * @private
  130. * @for Skylink
  131. * @since 0.5.2
  132. */
  133. Skylink.prototype._signalingServer = null;
  134.  
  135. /**
  136. * Stores the Signaling server protocol.
  137. * @attribute _signalingServerProtocol
  138. * @type String
  139. * @private
  140. * @for Skylink
  141. * @since 0.5.4
  142. */
  143. Skylink.prototype._signalingServerProtocol = window.location.protocol;
  144.  
  145. /**
  146. * Stores the Signaling server port.
  147. * @attribute _signalingServerPort
  148. * @type Number
  149. * @private
  150. * @for Skylink
  151. * @since 0.5.4
  152. */
  153. Skylink.prototype._signalingServerPort = null;
  154.  
  155. /**
  156. * Stores the Signaling socket connection object.
  157. * @attribute _socket
  158. * @type io
  159. * @private
  160. * @for Skylink
  161. * @since 0.1.0
  162. */
  163. Skylink.prototype._socket = null;
  164.  
  165. /**
  166. * Stores the socket connection timeout when establishing connection to the Signaling.
  167. * @attribute _socketTimeout
  168. * @type Number
  169. * @private
  170. * @for Skylink
  171. * @since 0.5.4
  172. */
  173. Skylink.prototype._socketTimeout = 0;
  174.  
  175. /**
  176. * Stores the flag that indicates if XDomainRequest is used for IE 8/9.
  177. * @attribute _socketUseXDR
  178. * @type Boolean
  179. * @private
  180. * @for Skylink
  181. * @since 0.5.4
  182. */
  183. Skylink.prototype._socketUseXDR = false;
  184.  
  185. /**
  186. * Function that sends a socket message over the socket connection to the Signaling.
  187. * @method _sendChannelMessage
  188. * @private
  189. * @for Skylink
  190. * @since 0.5.8
  191. */
  192. Skylink.prototype._sendChannelMessage = function(message) {
  193. var self = this;
  194. var interval = 1000;
  195. var throughput = 16;
  196.  
  197. if (!self._channelOpen) {
  198. return;
  199. }
  200.  
  201. var messageString = JSON.stringify(message);
  202.  
  203. var sendLater = function(){
  204. if (self._socketMessageQueue.length > 0){
  205.  
  206. if (self._socketMessageQueue.length<throughput){
  207.  
  208. log.debug([(message.target ? message.target : 'server'), null, null,
  209. 'Sending delayed message' + ((!message.target) ? 's' : '') + ' ->'], {
  210. type: self._SIG_MESSAGE_TYPE.GROUP,
  211. lists: self._socketMessageQueue.slice(0,self._socketMessageQueue.length),
  212. mid: self._user.sid,
  213. rid: self._room.id
  214. });
  215.  
  216. // fix for self._socket undefined errors in firefox
  217. if (self._socket) {
  218. self._socket.send({
  219. type: self._SIG_MESSAGE_TYPE.GROUP,
  220. lists: self._socketMessageQueue.splice(0,self._socketMessageQueue.length),
  221. mid: self._user.sid,
  222. rid: self._room.id
  223. });
  224. } else {
  225. log.error([(message.target ? message.target : 'server'), null, null,
  226. 'Dropping delayed message' + ((!message.target) ? 's' : '') +
  227. ' as socket object is no longer defined ->'], {
  228. type: self._SIG_MESSAGE_TYPE.GROUP,
  229. lists: self._socketMessageQueue.slice(0,self._socketMessageQueue.length),
  230. mid: self._user.sid,
  231. rid: self._room.id
  232. });
  233. }
  234.  
  235. clearTimeout(self._socketMessageTimeout);
  236. self._socketMessageTimeout = null;
  237.  
  238. }
  239. else{
  240.  
  241. log.debug([(message.target ? message.target : 'server'), null, null,
  242. 'Sending delayed message' + ((!message.target) ? 's' : '') + ' ->'], {
  243. type: self._SIG_MESSAGE_TYPE.GROUP,
  244. lists: self._socketMessageQueue.slice(0,throughput),
  245. mid: self._user.sid,
  246. rid: self._room.id
  247. });
  248.  
  249. // fix for self._socket undefined errors in firefox
  250. if (self._socket) {
  251. self._socket.send({
  252. type: self._SIG_MESSAGE_TYPE.GROUP,
  253. lists: self._socketMessageQueue.splice(0,throughput),
  254. mid: self._user.sid,
  255. rid: self._room.id
  256. });
  257. } else {
  258. log.error([(message.target ? message.target : 'server'), null, null,
  259. 'Dropping delayed message' + ((!message.target) ? 's' : '') +
  260. ' as socket object is no longer defined ->'], {
  261. type: self._SIG_MESSAGE_TYPE.GROUP,
  262. lists: self._socketMessageQueue.slice(0,throughput),
  263. mid: self._user.sid,
  264. rid: self._room.id
  265. });
  266. }
  267.  
  268. clearTimeout(self._socketMessageTimeout);
  269. self._socketMessageTimeout = null;
  270. self._socketMessageTimeout = setTimeout(sendLater,interval);
  271.  
  272. }
  273. self._timestamp.now = Date.now() || function() { return +new Date(); };
  274. }
  275. };
  276.  
  277. //Delay when messages are sent too rapidly
  278. if ((Date.now() || function() { return +new Date(); }) - self._timestamp.now < interval &&
  279. self._groupMessageList.indexOf(message.type) > -1) {
  280.  
  281. log.warn([(message.target ? message.target : 'server'), null, null,
  282. 'Messages fired too rapidly. Delaying.'], {
  283. interval: 1000,
  284. throughput: 16,
  285. message: message
  286. });
  287.  
  288. self._socketMessageQueue.push(messageString);
  289.  
  290. if (!self._socketMessageTimeout){
  291. self._socketMessageTimeout = setTimeout(sendLater,
  292. interval - ((Date.now() || function() { return +new Date(); })-self._timestamp.now));
  293. }
  294. return;
  295. }
  296.  
  297. log.debug([(message.target ? message.target : 'server'), null, null,
  298. 'Sending to peer' + ((!message.target) ? 's' : '') + ' ->'], message);
  299.  
  300. //Normal case when messages are sent not so rapidly
  301. self._socket.send(messageString);
  302. self._timestamp.now = Date.now() || function() { return +new Date(); };
  303.  
  304. };
  305.  
  306. /**
  307. * Function that creates and opens a socket connection to the Signaling.
  308. * @method _createSocket
  309. * @private
  310. * @for Skylink
  311. * @since 0.5.10
  312. */
  313. Skylink.prototype._createSocket = function (type) {
  314. var self = this;
  315.  
  316. var options = {
  317. forceNew: true,
  318. //'sync disconnect on unload' : true,
  319. reconnection: false
  320. };
  321.  
  322. var ports = self._socketPorts[self._signalingServerProtocol];
  323.  
  324. var connectionType = null;
  325.  
  326. // just beginning
  327. if (self._signalingServerPort === null) {
  328. self._signalingServerPort = ports[0];
  329. connectionType = self.SOCKET_FALLBACK.NON_FALLBACK;
  330.  
  331. // reached the end of the last port for the protocol type
  332. } else if ( ports.indexOf(self._signalingServerPort) === ports.length - 1 ) {
  333.  
  334. // re-refresh to long-polling port
  335. if (type === 'WebSocket') {
  336. type = 'Polling';
  337. self._signalingServerPort = ports[0];
  338.  
  339. } else if (type === 'Polling') {
  340. options.reconnection = true;
  341. options.reconnectionAttempts = 4;
  342. options.reconectionDelayMax = 1000;
  343. }
  344.  
  345. // move to the next port
  346. } else {
  347. self._signalingServerPort = ports[ ports.indexOf(self._signalingServerPort) + 1 ];
  348. }
  349.  
  350. var url = self._signalingServerProtocol + '//' + self._signalingServer + ':' + self._signalingServerPort;
  351. //'http://ec2-52-8-93-170.us-west-1.compute.amazonaws.com:6001';
  352.  
  353. if (type === 'WebSocket') {
  354. options.transports = ['websocket'];
  355. } else if (type === 'Polling') {
  356. options.transports = ['xhr-polling', 'jsonp-polling', 'polling'];
  357. }
  358.  
  359. // if socket instance already exists, exit
  360. if (self._socket) {
  361. self._socket.removeAllListeners('connect_error');
  362. self._socket.removeAllListeners('reconnect_attempt');
  363. self._socket.removeAllListeners('reconnect_error');
  364. self._socket.removeAllListeners('reconnect_failed');
  365. self._socket.removeAllListeners('connect');
  366. self._socket.removeAllListeners('reconnect');
  367. self._socket.removeAllListeners('error');
  368. self._socket.removeAllListeners('disconnect');
  369. self._socket.removeAllListeners('message');
  370. self._socket.disconnect();
  371. self._socket = null;
  372. }
  373.  
  374. self._channelOpen = false;
  375.  
  376. log.log('Opening channel with signaling server url:', {
  377. url: url,
  378. useXDR: self._socketUseXDR,
  379. options: options
  380. });
  381.  
  382. self._socketSession = {
  383. type: type,
  384. options: options,
  385. url: url
  386. };
  387.  
  388. self._socket = io.connect(url, options);
  389.  
  390. if (connectionType === null) {
  391. connectionType = self._signalingServerProtocol === 'http:' ?
  392. (type === 'Polling' ? self.SOCKET_FALLBACK.LONG_POLLING :
  393. self.SOCKET_FALLBACK.FALLBACK_PORT) :
  394. (type === 'Polling' ? self.SOCKET_FALLBACK.LONG_POLLING_SSL :
  395. self.SOCKET_FALLBACK.FALLBACK_SSL_PORT);
  396. }
  397.  
  398. self._socket.on('connect_error', function (error) {
  399. self._channelOpen = false;
  400.  
  401. self._trigger('socketError', self.SOCKET_ERROR.CONNECTION_FAILED,
  402. error, connectionType);
  403.  
  404. self._trigger('channelRetry', connectionType, 1);
  405.  
  406. if (options.reconnection === false) {
  407. self._createSocket(type);
  408. }
  409. });
  410.  
  411. self._socket.on('reconnect_attempt', function (attempt) {
  412. self._channelOpen = false;
  413. self._trigger('socketError', self.SOCKET_ERROR.RECONNECTION_ATTEMPT,
  414. attempt, connectionType);
  415.  
  416. self._trigger('channelRetry', connectionType, attempt);
  417. });
  418.  
  419. self._socket.on('reconnect_error', function (error) {
  420. self._channelOpen = false;
  421. self._trigger('socketError', self.SOCKET_ERROR.RECONNECTION_FAILED,
  422. error, connectionType);
  423. });
  424.  
  425. self._socket.on('reconnect_failed', function (error) {
  426. self._channelOpen = false;
  427. self._trigger('socketError', self.SOCKET_ERROR.RECONNECTION_ABORTED,
  428. error, connectionType);
  429. });
  430.  
  431. self._socket.on('connect', function () {
  432. if (!self._channelOpen) {
  433. self._channelOpen = true;
  434. self._trigger('channelOpen');
  435. log.log([null, 'Socket', null, 'Channel opened']);
  436. }
  437. });
  438.  
  439. self._socket.on('reconnect', function () {
  440. if (!self._channelOpen) {
  441. self._channelOpen = true;
  442. self._trigger('channelOpen');
  443. log.log([null, 'Socket', null, 'Channel opened']);
  444. }
  445. });
  446.  
  447. self._socket.on('error', function(error) {
  448. self._channelOpen = false;
  449. self._trigger('channelError', error);
  450. log.error([null, 'Socket', null, 'Exception occurred:'], error);
  451. });
  452.  
  453. self._socket.on('disconnect', function() {
  454. self._channelOpen = false;
  455. self._trigger('channelClose');
  456. log.log([null, 'Socket', null, 'Channel closed']);
  457.  
  458. if (self._inRoom) {
  459. self.leaveRoom(false);
  460. self._trigger('sessionDisconnect', self._user.sid, self.getPeerInfo());
  461. }
  462. });
  463.  
  464. self._socket.on('message', function(message) {
  465. log.log([null, 'Socket', null, 'Received message']);
  466. self._processSigMessage(message);
  467. });
  468. };
  469.  
  470. /**
  471. * Function that starts the socket connection to the Signaling.
  472. * This starts creating the socket connection and called at first not when requiring to fallback.
  473. * @method _openChannel
  474. * @private
  475. * @for Skylink
  476. * @since 0.5.5
  477. */
  478. Skylink.prototype._openChannel = function() {
  479. var self = this;
  480. if (self._channelOpen) {
  481. log.error([null, 'Socket', null, 'Unable to instantiate a new channel connection ' +
  482. 'as there is already an ongoing channel connection']);
  483. return;
  484. }
  485.  
  486. if (self._readyState !== self.READY_STATE_CHANGE.COMPLETED) {
  487. log.error([null, 'Socket', null, 'Unable to instantiate a new channel connection ' +
  488. 'as readyState is not ready']);
  489. return;
  490. }
  491.  
  492. // set if forceSSL
  493. if (self._forceSSL) {
  494. self._signalingServerProtocol = 'https:';
  495. } else {
  496. self._signalingServerProtocol = window.location.protocol;
  497. }
  498.  
  499. var socketType = 'WebSocket';
  500.  
  501. // For IE < 9 that doesn't support WebSocket
  502. if (!window.WebSocket) {
  503. socketType = 'Polling';
  504. }
  505.  
  506. self._signalingServerPort = null;
  507.  
  508. // Begin with a websocket connection
  509. self._createSocket(socketType);
  510. };
  511.  
  512. /**
  513. * Function that stops the socket connection to the Signaling.
  514. * @method _closeChannel
  515. * @private
  516. * @for Skylink
  517. * @since 0.5.5
  518. */
  519. Skylink.prototype._closeChannel = function() {
  520. if (!this._channelOpen) {
  521. return;
  522. }
  523. if (this._socket) {
  524. this._socket.removeAllListeners('connect_error');
  525. this._socket.removeAllListeners('reconnect_attempt');
  526. this._socket.removeAllListeners('reconnect_error');
  527. this._socket.removeAllListeners('reconnect_failed');
  528. this._socket.removeAllListeners('connect');
  529. this._socket.removeAllListeners('reconnect');
  530. this._socket.removeAllListeners('error');
  531. this._socket.removeAllListeners('disconnect');
  532. this._socket.removeAllListeners('message');
  533. this._socket.disconnect();
  534. this._socket = null;
  535. }
  536. this._channelOpen = false;
  537. this._trigger('channelClose');
  538. };