From dc0a7e7628fd427ec7afb3954e4e638376b862b5 Mon Sep 17 00:00:00 2001 From: hristoterezov Date: Wed, 16 Nov 2016 15:02:32 -0600 Subject: [PATCH] feat(Analytics): Multiple analytics handlers support --- analytics.js | 4 +- conference.js | 30 ++------ modules/analytics/analytics.js | 128 +++++++++++++++++++++++++++++++++ modules/config/URLProcessor.js | 2 +- modules/settings/Settings.js | 5 -- 5 files changed, 139 insertions(+), 30 deletions(-) create mode 100644 modules/analytics/analytics.js diff --git a/analytics.js b/analytics.js index 7f270fd4e..fca72026d 100644 --- a/analytics.js +++ b/analytics.js @@ -27,5 +27,7 @@ action + '.' + data.browserName, label, value); }; - ctx.Analytics = Analytics; + if(typeof ctx.analyticsHandlers === "undefined") + ctx.analyticsHandlers = []; + ctx.analyticsHandlers.push(Analytics); }(window)); diff --git a/conference.js b/conference.js index 1089d2cfe..a3409ca4c 100644 --- a/conference.js +++ b/conference.js @@ -13,6 +13,8 @@ import {reportError} from './modules/util/helpers'; import UIEvents from './service/UI/UIEvents'; import UIUtil from './modules/UI/util/UIUtil'; +import analytics from './modules/analytics/analytics'; + const ConnectionEvents = JitsiMeetJS.events.connection; const ConnectionErrors = JitsiMeetJS.errors.connection; @@ -438,26 +440,6 @@ function disconnect() { return Promise.resolve(); } -/** - * Set permanent properties to analytics. - * NOTE: Has to be used after JitsiMeetJS.init. Otherwise analytics will be - * null. - */ -function setAnalyticsPermanentProperties() { - let permanentProperties = { - userAgent: navigator.userAgent, - roomName: APP.conference.roomName - }; - let {server, group} = APP.tokenData; - if(server) { - permanentProperties.server = server; - } - if(group) { - permanentProperties.group = group; - } - JitsiMeetJS.analytics.addPermanentProperties(permanentProperties); -} - export default { isModerator: false, audioMuted: false, @@ -504,9 +486,11 @@ export default { }; } - return JitsiMeetJS.init(config) - .then(() => { - setAnalyticsPermanentProperties(); + return JitsiMeetJS.init( + Object.assign( + {enableAnalyticsLogging: analytics.isEnabled()}, config) + ).then(() => { + analytics.init(); return createInitialLocalTracksAndConnect(options.roomName); }).then(([tracks, con]) => { console.log('initialized with %s local tracks', tracks.length); diff --git a/modules/analytics/analytics.js b/modules/analytics/analytics.js new file mode 100644 index 000000000..219fbae57 --- /dev/null +++ b/modules/analytics/analytics.js @@ -0,0 +1,128 @@ +/* global JitsiMeetJS, config, APP */ +/** + * 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. + * @param {string} url the url to be loaded + * @returns {Promise} resolved with no arguments when the script is loaded and + * rejected with the error from JitsiMeetJS.ScriptUtil.loadScript method + */ +function loadScript(url) { + return new Promise((resolve, reject) => + JitsiMeetJS.util.ScriptUtil.loadScript( + url, + /* async */ true, + /* prepend */ false, + /* relativeURL */ false, + /* loadCallback */ () => resolve(), + /* errorCallback */ error => reject(error))); +} + +/** + * Handles the initialization of analytics. + */ +class Analytics { + constructor() { + this._scriptURLs = Array.isArray(config.analyticsScriptUrls) + ? config.analyticsScriptUrls : []; + this._enabled = !!this._scriptURLs.length + && !config.disableThirdPartyRequests; + window.analyticsHandlers = []; + const machineId = JitsiMeetJS.getMachineId(); + this._handlerConstructorOptions = { + product: "lib-jitsi-meet", + version: JitsiMeetJS.version, + session: machineId, + user: "uid-" + machineId + }; + } + + /** + * Returns whether analytics is enabled or not. + * @returns {boolean} whether analytics is enabled or not. + */ + isEnabled() { + return this._enabled; + } + + /** + * Tries to load the scripts for the analytics handlers. + * @returns {Promise} resolves with the handlers that have been + * successfully loaded and rejects if there are no handlers loaded or the + * analytics is disabled. + */ + _loadHandlers() { + if(!this.isEnabled()) { + return Promise.reject(new Error("Analytics is disabled!")); + } + let handlersPromises = []; + this._scriptURLs.forEach(url => + handlersPromises.push( + loadScript(url).then( + () => { + return {type: "success"}; + }, + error => { + return {type: "error", error, url}; + })) + ); + return new Promise((resolve, reject) => + { + Promise.all(handlersPromises).then(values => { + values.forEach(el => { + if(el.type === "error") { + console.log("Fialed to load " + el.url); + console.error(el.error); + } + }); + + if(window.analyticsHandlers.length === 0) { + reject(new Error("No analytics handlers available")); + } else { + let handlerInstances = []; + window.analyticsHandlers.forEach( + Handler => handlerInstances.push( + new Handler(this._handlerConstructorOptions))); + resolve(handlerInstances); + } + }); + }); + } + + /** + * Loads the analytics scripts and inits JitsiMeetJS.analytics by setting + * permanent properties and setting the handlers from the loaded scripts. + * NOTE: Has to be used after JitsiMeetJS.init. Otherwise analytics will be + * null. + */ + init() { + let analytics = JitsiMeetJS.analytics; + if(!this.isEnabled() || !analytics) + return; + + this._loadHandlers() + .then(handlers => { + let permanentProperties = { + userAgent: navigator.userAgent, + roomName: APP.conference.roomName + }; + let {server, group} = APP.tokenData; + if(server) { + permanentProperties.server = server; + } + if(group) { + permanentProperties.group = group; + } + analytics.addPermanentProperties(permanentProperties); + analytics.setAnalyticsHandlers(handlers); + }, error => analytics.dispose() && console.error(error)); + + } +} + +export default new Analytics(); diff --git a/modules/config/URLProcessor.js b/modules/config/URLProcessor.js index 5befe603e..a1de4176d 100644 --- a/modules/config/URLProcessor.js +++ b/modules/config/URLProcessor.js @@ -38,7 +38,7 @@ var URLProcessor = { confKey = key.substr("config.".length); // prevent passing some parameters which can inject scripts - if (confKey === 'analyticsScriptUrl' + if (confKey === 'analyticsScriptUrls' || confKey === 'callStatsCustomScriptUrl') continue; diff --git a/modules/settings/Settings.js b/modules/settings/Settings.js index 66368b34f..5c42cfa75 100644 --- a/modules/settings/Settings.js +++ b/modules/settings/Settings.js @@ -9,11 +9,6 @@ function generateUniqueId() { return _p8() + _p8() + _p8() + _p8(); } -if (!jitsiLocalStorage.getItem("jitsiMeetId")) { - jitsiLocalStorage.setItem("jitsiMeetId",generateUniqueId()); - console.log("generated id", jitsiLocalStorage.getItem("jitsiMeetId")); -} - let avatarUrl = ''; let email = UIUtil.unescapeHtml(jitsiLocalStorage.getItem("email") || '');