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.0KB

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