You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CallStats.js 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. /* global config, $, APP, Strophe, callstats */
  2. var Settings = require('../settings/Settings');
  3. var jsSHA = require('jssha');
  4. var io = require('socket.io-client');
  5. var callStats = null;
  6. /**
  7. * @const
  8. * @see http://www.callstats.io/api/#enumeration-of-wrtcfuncnames
  9. */
  10. var wrtcFuncNames = {
  11. createOffer: "createOffer",
  12. createAnswer: "createAnswer",
  13. setLocalDescription: "setLocalDescription",
  14. setRemoteDescription: "setRemoteDescription",
  15. addIceCandidate: "addIceCandidate",
  16. getUserMedia: "getUserMedia"
  17. };
  18. /**
  19. * Some errors may occur before CallStats.init in which case we will accumulate
  20. * them and submit them to callstats.io on CallStats.init.
  21. */
  22. var pendingErrors = [];
  23. function initCallback (err, msg) {
  24. console.log("CallStats Status: err=" + err + " msg=" + msg);
  25. }
  26. /**
  27. * The indicator which determines whether the integration of callstats.io is
  28. * enabled/allowed. Its value does not indicate whether the integration will
  29. * succeed at runtime but rather whether it is to be attempted at runtime at
  30. * all.
  31. */
  32. var _enabled
  33. = config.callStatsID && config.callStatsSecret
  34. // Even though AppID and AppSecret may be specified, the integration of
  35. // callstats.io may be disabled because of globally-disallowed requests
  36. // to any third parties.
  37. && (config.disableThirdPartyRequests !== true);
  38. if (_enabled) {
  39. // Since callstats.io is a third party, we cannot guarantee the quality of
  40. // their service. More specifically, their server may take noticeably long
  41. // time to respond. Consequently, it is in our best interest (in the sense
  42. // that the intergration of callstats.io is pretty important to us but not
  43. // enough to allow it to prevent people from joining a conference) to (1)
  44. // start downloading their API as soon as possible and (2) do the
  45. // downloading asynchronously.
  46. (function (d, src) {
  47. var elementName = 'script';
  48. var newScript = d.createElement(elementName);
  49. var referenceNode = d.getElementsByTagName(elementName)[0];
  50. newScript.async = true;
  51. newScript.src = src;
  52. referenceNode.parentNode.insertBefore(newScript, referenceNode);
  53. })(document, 'https://api.callstats.io/static/callstats.min.js');
  54. // FIXME At the time of this writing, we hope that the callstats.io API will
  55. // have loaded by the time we needed it (i.e. CallStats.init is invoked).
  56. }
  57. /**
  58. * Returns a function which invokes f in a try/catch block, logs any exception
  59. * to the console, and then swallows it.
  60. *
  61. * @param f the function to invoke in a try/catch block
  62. * @return a function which invokes f in a try/catch block, logs any exception
  63. * to the console, and then swallows it
  64. */
  65. function _try_catch (f) {
  66. return function () {
  67. try {
  68. f.apply(this, arguments);
  69. } catch (e) {
  70. console.error(e);
  71. }
  72. };
  73. }
  74. var CallStats = {
  75. init: _try_catch(function (jingleSession) {
  76. if(!this.isEnabled() || callStats !== null) {
  77. return;
  78. }
  79. try {
  80. callStats = new callstats($, io, jsSHA);
  81. this.session = jingleSession;
  82. this.peerconnection = jingleSession.peerconnection.peerconnection;
  83. this.userID = Settings.getCallStatsUserName();
  84. var location = window.location;
  85. this.confID = location.hostname + location.pathname;
  86. callStats.initialize(
  87. config.callStatsID, config.callStatsSecret,
  88. this.userID /* generated or given by the origin server */,
  89. initCallback);
  90. var usage = callStats.fabricUsage.multiplex;
  91. callStats.addNewFabric(
  92. this.peerconnection,
  93. Strophe.getResourceFromJid(jingleSession.peerjid),
  94. usage,
  95. this.confID,
  96. this.pcCallback.bind(this));
  97. } catch (e) {
  98. // The callstats.io API failed to initialize (e.g. because its
  99. // download failed to succeed in general or on time). Further
  100. // attempts to utilize it cannot possibly succeed.
  101. callStats = null;
  102. console.error(e);
  103. }
  104. // Notify callstats about pre-init failures if there were any.
  105. if (callStats && pendingErrors.length) {
  106. pendingErrors.forEach(function (error) {
  107. this._reportError(error.type, error.error, error.pc);
  108. }, this);
  109. pendingErrors.length = 0;
  110. }
  111. }),
  112. /**
  113. * Returns true if the callstats integration is enabled, otherwise returns
  114. * false.
  115. *
  116. * @returns true if the callstats integration is enabled, otherwise returns
  117. * false.
  118. */
  119. isEnabled: function() {
  120. return _enabled;
  121. },
  122. pcCallback: _try_catch(function (err, msg) {
  123. if (!callStats) {
  124. return;
  125. }
  126. console.log("Monitoring status: "+ err + " msg: " + msg);
  127. callStats.sendFabricEvent(this.peerconnection,
  128. callStats.fabricEvent.fabricSetup, this.confID);
  129. }),
  130. sendMuteEvent: _try_catch(function (mute, type) {
  131. if (!callStats) {
  132. return;
  133. }
  134. var event = null;
  135. if (type === "video") {
  136. event = (mute? callStats.fabricEvent.videoPause :
  137. callStats.fabricEvent.videoResume);
  138. }
  139. else {
  140. event = (mute? callStats.fabricEvent.audioMute :
  141. callStats.fabricEvent.audioUnmute);
  142. }
  143. callStats.sendFabricEvent(this.peerconnection, event, this.confID);
  144. }),
  145. sendTerminateEvent: _try_catch(function () {
  146. if(!callStats) {
  147. return;
  148. }
  149. callStats.sendFabricEvent(this.peerconnection,
  150. callStats.fabricEvent.fabricTerminated, this.confID);
  151. }),
  152. sendSetupFailedEvent: _try_catch(function () {
  153. if(!callStats) {
  154. return;
  155. }
  156. callStats.sendFabricEvent(this.peerconnection,
  157. callStats.fabricEvent.fabricSetupFailed, this.confID);
  158. }),
  159. /**
  160. * Sends the given feedback through CallStats.
  161. *
  162. * @param overallFeedback an integer between 1 and 5 indicating the
  163. * user feedback
  164. * @param detailedFeedback detailed feedback from the user. Not yet used
  165. */
  166. sendFeedback: _try_catch(function(overallFeedback, detailedFeedback) {
  167. if(!callStats) {
  168. return;
  169. }
  170. var feedbackString = '{"userID":"' + this.userID + '"' +
  171. ', "overall":' + overallFeedback +
  172. ', "comment": "' + detailedFeedback + '"}';
  173. var feedbackJSON = JSON.parse(feedbackString);
  174. callStats.sendUserFeedback(this.confID, feedbackJSON);
  175. }),
  176. /**
  177. * Reports an error to callstats.
  178. *
  179. * @param type the type of the error, which will be one of the wrtcFuncNames
  180. * @param e the error
  181. * @param pc the peerconnection
  182. * @private
  183. */
  184. _reportError: function (type, e, pc) {
  185. if (callStats) {
  186. callStats.reportError(pc, this.confID, type, e);
  187. } else if (this.isEnabled()) {
  188. pendingErrors.push({ type: type, error: e, pc: pc });
  189. }
  190. // else just ignore it
  191. },
  192. /**
  193. * Notifies CallStats that getUserMedia failed.
  194. *
  195. * @param {Error} e error to send
  196. */
  197. sendGetUserMediaFailed: _try_catch(function (e) {
  198. this._reportError(wrtcFuncNames.getUserMedia, e, null);
  199. }),
  200. /**
  201. * Notifies CallStats that peer connection failed to create offer.
  202. *
  203. * @param {Error} e error to send
  204. * @param {RTCPeerConnection} pc connection on which failure occured.
  205. */
  206. sendCreateOfferFailed: _try_catch(function (e, pc) {
  207. this._reportError(wrtcFuncNames.createOffer, e, pc);
  208. }),
  209. /**
  210. * Notifies CallStats that peer connection failed to create answer.
  211. *
  212. * @param {Error} e error to send
  213. * @param {RTCPeerConnection} pc connection on which failure occured.
  214. */
  215. sendCreateAnswerFailed: _try_catch(function (e, pc) {
  216. this._reportError(wrtcFuncNames.createAnswer, e, pc);
  217. }),
  218. /**
  219. * Notifies CallStats that peer connection failed to set local description.
  220. *
  221. * @param {Error} e error to send
  222. * @param {RTCPeerConnection} pc connection on which failure occured.
  223. */
  224. sendSetLocalDescFailed: _try_catch(function (e, pc) {
  225. this._reportError(wrtcFuncNames.setLocalDescription, e, pc);
  226. }),
  227. /**
  228. * Notifies CallStats that peer connection failed to set remote description.
  229. *
  230. * @param {Error} e error to send
  231. * @param {RTCPeerConnection} pc connection on which failure occured.
  232. */
  233. sendSetRemoteDescFailed: _try_catch(function (e, pc) {
  234. this._reportError(wrtcFuncNames.setRemoteDescription, e, pc);
  235. }),
  236. /**
  237. * Notifies CallStats that peer connection failed to add ICE candidate.
  238. *
  239. * @param {Error} e error to send
  240. * @param {RTCPeerConnection} pc connection on which failure occured.
  241. */
  242. sendAddIceCandidateFailed: _try_catch(function (e, pc) {
  243. this._reportError(wrtcFuncNames.addIceCandidate, e, pc);
  244. })
  245. };
  246. module.exports = CallStats;