From b49a08c485c0eb02dd2354830c09bddcfb48922c Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Fri, 15 Jan 2016 15:42:04 +0200 Subject: [PATCH 1/2] Decides whether to use analytics after the analytics API has been given a chance to load. --- index.html | 19 -------------- modules/statistics/AnalyticsAdapter.js | 36 +++++++++++++++++++++++--- modules/statistics/CallStats.js | 14 ++++------ modules/util/ScriptUtil.js | 32 +++++++++++++++++++++++ 4 files changed, 69 insertions(+), 32 deletions(-) create mode 100644 modules/util/ScriptUtil.js diff --git a/index.html b/index.html index f317246f3..83cd96291 100644 --- a/index.html +++ b/index.html @@ -222,24 +222,5 @@ - diff --git a/modules/statistics/AnalyticsAdapter.js b/modules/statistics/AnalyticsAdapter.js index 8492c1123..e57c2caa6 100644 --- a/modules/statistics/AnalyticsAdapter.js +++ b/modules/statistics/AnalyticsAdapter.js @@ -1,15 +1,43 @@ +var ScriptUtil = require('../util/ScriptUtil'); + +// Load the integration of a third-party analytics API such as Google Analytics. +// Since we cannot guarantee the quality of the third-party service (e.g. their +// server may take noticeably long time to respond), it is in our best interest +// (in the sense that the intergration of the analytics API is important to us +// but not enough to allow it to prevent people from joining a conference) to +// download the API asynchronously. Additionally, Google Analytics will download +// its implementation asynchronously anyway so it makes sense to append the +// loading on our side rather than prepend it. +if (config.disableThirdPartyRequests !== true) { + ScriptUtil.loadScript( + 'analytics.js?v=1', + /* async */ true, + /* prepend */ false); +} + +// NoopAnalytics function NoopAnalytics() {} + NoopAnalytics.prototype.sendEvent = function () {}; +// AnalyticsAdapter function AnalyticsAdapter() { - var AnalyticsImpl = window.Analytics || NoopAnalytics; - this.analytics = new AnalyticsImpl(); + // XXX Since we asynchronously load the integration of the analytics API and + // the analytics API may asynchronously load its implementation (e.g. Google + // Analytics), we cannot make the decision with respect to which analytics + // implementation we will use here and we have to postpone it i.e. we will + // make a lazy decision. } AnalyticsAdapter.prototype.sendEvent = function (action, data) { + var a = this.analytics; + + if (a === null || typeof a === 'undefined') { + this.analytics = a = new (window.Analytics || NoopAnalytics)(); + } try { - this.analytics.sendEvent.apply(this.analytics, arguments); + a.sendEvent.apply(a, arguments); } catch (ignored) {} }; -module.exports = new AnalyticsAdapter(); \ No newline at end of file +module.exports = new AnalyticsAdapter(); diff --git a/modules/statistics/CallStats.js b/modules/statistics/CallStats.js index 7529c71dc..a5b3c1596 100644 --- a/modules/statistics/CallStats.js +++ b/modules/statistics/CallStats.js @@ -1,6 +1,7 @@ /* global config, $, APP, Strophe, callstats */ var Settings = require('../settings/Settings'); +var ScriptUtil = require('../util/ScriptUtil'); var jsSHA = require('jssha'); var io = require('socket.io-client'); var callStats = null; @@ -49,15 +50,10 @@ if (_enabled) { // enough to allow it to prevent people from joining a conference) to (1) // start downloading their API as soon as possible and (2) do the // downloading asynchronously. - (function (d, src) { - var elementName = 'script'; - var newScript = d.createElement(elementName); - var referenceNode = d.getElementsByTagName(elementName)[0]; - - newScript.async = true; - newScript.src = src; - referenceNode.parentNode.insertBefore(newScript, referenceNode); - })(document, 'https://api.callstats.io/static/callstats.min.js'); + ScriptUtil.loadScript( + 'https://api.callstats.io/static/callstats.min.js', + /* async */ true, + /* prepend */ true); // FIXME At the time of this writing, we hope that the callstats.io API will // have loaded by the time we needed it (i.e. CallStats.init is invoked). } diff --git a/modules/util/ScriptUtil.js b/modules/util/ScriptUtil.js new file mode 100644 index 000000000..0c8941d2a --- /dev/null +++ b/modules/util/ScriptUtil.js @@ -0,0 +1,32 @@ +/** + * Implements utility functions which facilitate the dealing with scripts such + * as the download and execution of a JavaScript file. + */ +var ScriptUtil = { + /** + * Loads a script from a specific source. + * + * @param src the source from the which the script is to be (down)loaded + * @param async true to asynchronously load the script or false to + * synchronously load the script + * @param prepend true to schedule the loading of the script as soon as + * possible or false to schedule the loading of the script at the end of the + * scripts known at the time + */ + loadScript: function (src, async, prepend) { + var d = document; + var tagName = 'script'; + var script = d.createElement(tagName); + var referenceNode = d.getElementsByTagName(tagName)[0]; + + script.async = async; + script.src = src; + if (prepend) { + referenceNode.parentNode.insertBefore(script, referenceNode); + } else { + referenceNode.parentNode.appendChild(script); + } + }, +}; + +module.exports = ScriptUtil; From ec954ad3cc670e2be6776b7e89a1943a31865b6f Mon Sep 17 00:00:00 2001 From: Lyubomir Marinov Date: Fri, 15 Jan 2016 15:42:04 +0200 Subject: [PATCH 2/2] Decides whether to use analytics after the analytics API has been given a chance to load. --- modules/statistics/AnalyticsAdapter.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/statistics/AnalyticsAdapter.js b/modules/statistics/AnalyticsAdapter.js index e57c2caa6..11cd92df0 100644 --- a/modules/statistics/AnalyticsAdapter.js +++ b/modules/statistics/AnalyticsAdapter.js @@ -1,3 +1,5 @@ +/* global config */ + var ScriptUtil = require('../util/ScriptUtil'); // Load the integration of a third-party analytics API such as Google Analytics. @@ -33,7 +35,9 @@ AnalyticsAdapter.prototype.sendEvent = function (action, data) { var a = this.analytics; if (a === null || typeof a === 'undefined') { - this.analytics = a = new (window.Analytics || NoopAnalytics)(); + var AnalyticsImpl = window.Analytics || NoopAnalytics; + + this.analytics = a = new AnalyticsImpl(); } try { a.sendEvent.apply(a, arguments);