diff --git a/react/features/analytics/functions.js b/react/features/analytics/functions.js index 757ec5f91..0e2a1ef93 100644 --- a/react/features/analytics/functions.js +++ b/react/features/analytics/functions.js @@ -35,28 +35,23 @@ export function resetAnalytics() { } /** - * 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. + * Creates the analytics handlers. * - * @param {Store} store - The redux store in which the specified {@code action} - * is being dispatched. - * @returns {void} + * @param {Store} store - The redux store in which the specified {@code action} is being dispatched. + * @returns {Promise} Resolves with the handlers that have been successfully loaded. */ -export function initAnalytics({ getState }: { getState: Function }) { +export function createHandlers({ getState }: { getState: Function }) { getJitsiMeetGlobalNS().analyticsHandlers = []; window.analyticsHandlers = []; // Legacy support. - if (!analytics || !isAnalyticsEnabled(getState)) { - return; + if (!isAnalyticsEnabled(getState)) { + return Promise.resolve([]); } const state = getState(); const config = state['features/base/config']; const { locationURL } = state['features/base/connection']; const host = locationURL ? locationURL.host : ''; - const { analytics: analyticsConfig = {}, deploymentInfo @@ -68,7 +63,7 @@ export function initAnalytics({ getState }: { getState: Function }) { googleAnalyticsTrackingId, whiteListedEvents } = analyticsConfig; - const { group, server, user } = state['features/base/jwt']; + const { group, user } = state['features/base/jwt']; const handlerConstructorOptions = { amplitudeAPPKey, blackListedEvents, @@ -82,54 +77,93 @@ export function initAnalytics({ getState }: { getState: Function }) { version: JitsiMeetJS.version, whiteListedEvents }; + const handlers = []; - _loadHandlers(scriptURLs, handlerConstructorOptions) - .then(handlers => { - const roomName = state['features/base/conference'].room; - const permanentProperties = {}; + try { + const amplitude = new AmplitudeHandler(handlerConstructorOptions); - if (server) { - permanentProperties.server = server; - } - if (group) { - permanentProperties.group = group; - } + handlers.push(amplitude); + // eslint-disable-next-line no-empty + } catch (e) {} - // Report if user is using websocket - permanentProperties.websocket = navigator.product !== 'ReactNative' && typeof config.websocket === 'string'; - - // Optionally, include local deployment information based on the - // contents of window.config.deploymentInfo. - if (deploymentInfo) { - for (const key in deploymentInfo) { - if (deploymentInfo.hasOwnProperty(key)) { - permanentProperties[key] = deploymentInfo[key]; - } + return ( + _loadHandlers(scriptURLs, handlerConstructorOptions) + .then(externalHandlers => { + handlers.push(...externalHandlers); + if (handlers.length === 0) { + // Throwing an error in order to dispose the analytics in the catch clause due to the lack of any + // analytics handlers. + throw new Error('No analytics handlers created!'); } - } - analytics.addPermanentProperties(permanentProperties); - analytics.setConferenceName(roomName); + return handlers; + }) + .catch(e => { + analytics.dispose(); + logger.error(e); + + return []; + })); - // Set the handlers last, since this triggers emptying of the cache - analytics.setAnalyticsHandlers(handlers); - }) - .catch(error => { - analytics.dispose(); - logger.error(error); - }); } /** - * Tries to load the scripts for the analytics handlers. + * 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. + * + * @param {Store} store - The redux store in which the specified {@code action} is being dispatched. + * @param {Array} handlers - The analytics handlers. + * @returns {void} + */ +export function initAnalytics({ getState }: { getState: Function }, handlers: Array) { + if (!isAnalyticsEnabled(getState) || handlers.length === 0) { + return; + } + + const state = getState(); + const config = state['features/base/config']; + const { + deploymentInfo + } = config; + const { group, server } = state['features/base/jwt']; + const roomName = state['features/base/conference'].room; + const permanentProperties = {}; + + if (server) { + permanentProperties.server = server; + } + if (group) { + permanentProperties.group = group; + } + + // Report if user is using websocket + permanentProperties.websocket = navigator.product !== 'ReactNative' && typeof config.websocket === 'string'; + + // Optionally, include local deployment information based on the + // contents of window.config.deploymentInfo. + if (deploymentInfo) { + for (const key in deploymentInfo) { + if (deploymentInfo.hasOwnProperty(key)) { + permanentProperties[key] = deploymentInfo[key]; + } + } + } + + analytics.addPermanentProperties(permanentProperties); + analytics.setConferenceName(roomName); + + // Set the handlers last, since this triggers emptying of the cache + analytics.setAnalyticsHandlers(handlers); +} + +/** + * Tries to load the scripts for the analytics handlers and creates them. * * @param {Array} scriptURLs - The array of script urls to load. - * @param {Object} handlerConstructorOptions - The default options to pass when - * creating handlers. + * @param {Object} handlerConstructorOptions - The default options to pass when creating handlers. * @private - * @returns {Promise} Resolves with the handlers that have been - * successfully loaded and rejects if there are no handlers loaded or the - * analytics is disabled. + * @returns {Promise} Resolves with the handlers that have been successfully loaded and rejects if there are no handlers + * loaded or the analytics is disabled. */ function _loadHandlers(scriptURLs = [], handlerConstructorOptions) { const promises = []; @@ -161,13 +195,8 @@ function _loadHandlers(scriptURLs = [], handlerConstructorOptions) { // check the old location to provide legacy support const analyticsHandlers = [ ...getJitsiMeetGlobalNS().analyticsHandlers, - ...window.analyticsHandlers, - - // NOTE: when we add second handler it will be good to put all - // build-in handlers in an array and destruct it here. - AmplitudeHandler + ...window.analyticsHandlers ]; - const handlers = []; for (const Handler of analyticsHandlers) { @@ -179,7 +208,6 @@ function _loadHandlers(scriptURLs = [], handlerConstructorOptions) { logger.warn(`Error creating analytics handler: ${error}`); } } - logger.debug(`Loaded ${handlers.length} analytics handlers`); return handlers; diff --git a/react/features/analytics/handlers/AmplitudeHandler.js b/react/features/analytics/handlers/AmplitudeHandler.js index fcc198512..6f64dca65 100644 --- a/react/features/analytics/handlers/AmplitudeHandler.js +++ b/react/features/analytics/handlers/AmplitudeHandler.js @@ -1,5 +1,5 @@ import AbstractHandler from './AbstractHandler'; -import { amplitude } from './amplitude'; +import { amplitude, fixDeviceID } from './amplitude'; /** * Analytics handler for Amplitude. @@ -28,7 +28,7 @@ export default class AmplitudeHandler extends AbstractHandler { }; amplitude.getInstance(this._amplitudeOptions).init(amplitudeAPPKey, undefined, { includeReferrer: true }); - amplitude.fixDeviceID(this._amplitudeOptions); + fixDeviceID(amplitude.getInstance(this._amplitudeOptions)); if (user) { amplitude.getInstance(this._amplitudeOptions).setUserId(user); diff --git a/react/features/analytics/handlers/amplitude/Amplitude.native.js b/react/features/analytics/handlers/amplitude/Amplitude.native.js index b19c5f6ae..56dbd2ced 100644 --- a/react/features/analytics/handlers/amplitude/Amplitude.native.js +++ b/react/features/analytics/handlers/amplitude/Amplitude.native.js @@ -111,12 +111,5 @@ export default { } return instance; - }, - - /** - * Currently not implemented. - * - * @returns {void} - */ - fixDeviceID() { } // eslint-disable-line no-empty-function + } }; diff --git a/react/features/analytics/handlers/amplitude/Amplitude.web.js b/react/features/analytics/handlers/amplitude/Amplitude.web.js index b2b280cca..21c01c6f0 100644 --- a/react/features/analytics/handlers/amplitude/Amplitude.web.js +++ b/react/features/analytics/handlers/amplitude/Amplitude.web.js @@ -10,29 +10,5 @@ export default { */ getInstance(options = {}) { return amplitude.getInstance(options.instanceName); - }, - - /** - * Sets the device id to the value of __AMDID cookie or sets the __AMDID cookie value to the current device id in - * case the __AMDID cookie is not set. - * - * @param {*} options - Optional parameters. - * @property {string} options.instanceName - The name of the AmplitudeClient instance. - * @property {string} options.host - The host from the original URL. - * @returns {void} - */ - fixDeviceID(options) { - const deviceId = document.cookie.replace(/(?:(?:^|.*;\s*)__AMDID\s*=\s*([^;]*).*$)|^.*$/, '$1'); - const instance = this.getInstance(options); - - if (deviceId === '') { - const { host = '' } = options; - - document.cookie - = `__AMDID=${instance.options.deviceId};max-age=630720000${ - host === '' ? '' : `;domain=.${host}`}`; // max-age=10 years - } else { - instance.setDeviceId(deviceId); - } } }; diff --git a/react/features/analytics/handlers/amplitude/fixDeviceID.native.js b/react/features/analytics/handlers/amplitude/fixDeviceID.native.js new file mode 100644 index 000000000..04a46c9d4 --- /dev/null +++ b/react/features/analytics/handlers/amplitude/fixDeviceID.native.js @@ -0,0 +1,9 @@ +/** + * Custom logic for setting the correct device id. + * + * @param {AmplitudeClient} amplitude - The amplitude instance. + * @returns {void} + */ +export function fixDeviceID(amplitude) { // eslint-disable-line no-unused-vars + +} diff --git a/react/features/analytics/handlers/amplitude/fixDeviceID.web.js b/react/features/analytics/handlers/amplitude/fixDeviceID.web.js new file mode 100644 index 000000000..04a46c9d4 --- /dev/null +++ b/react/features/analytics/handlers/amplitude/fixDeviceID.web.js @@ -0,0 +1,9 @@ +/** + * Custom logic for setting the correct device id. + * + * @param {AmplitudeClient} amplitude - The amplitude instance. + * @returns {void} + */ +export function fixDeviceID(amplitude) { // eslint-disable-line no-unused-vars + +} diff --git a/react/features/analytics/handlers/amplitude/index.js b/react/features/analytics/handlers/amplitude/index.js index ec63cbe22..80d88bb7a 100644 --- a/react/features/analytics/handlers/amplitude/index.js +++ b/react/features/analytics/handlers/amplitude/index.js @@ -1 +1,2 @@ export { default as amplitude } from './Amplitude'; +export * from './fixDeviceID'; diff --git a/react/features/analytics/middleware.js b/react/features/analytics/middleware.js index f3198429b..7b3c9344f 100644 --- a/react/features/analytics/middleware.js +++ b/react/features/analytics/middleware.js @@ -18,7 +18,7 @@ import { import { UPDATE_LOCAL_TRACKS_DURATION } from './actionTypes'; import { createLocalTracksDurationEvent, createNetworkInfoEvent } from './AnalyticsEvents'; -import { initAnalytics, resetAnalytics, sendAnalytics } from './functions'; +import { createHandlers, initAnalytics, resetAnalytics, sendAnalytics } from './functions'; /** * Calculates the duration of the local tracks. @@ -79,13 +79,28 @@ function calculateLocalTrackDuration(state) { * @returns {Function} */ MiddlewareRegistry.register(store => next => action => { - if (action.type === SET_CONFIG) { + switch (action.type) { + case SET_CONFIG: if (navigator.product === 'ReactNative') { // Reseting the analytics is currently not needed for web because // the user will be redirected to another page and new instance of // Analytics will be created and initialized. resetAnalytics(); } + break; + case SET_ROOM: { + // createHandlers is called before the SET_ROOM action is executed in order for Amplitude to initialize before + // the deeplinking logic is executed (after the SET_ROOM action) so that the Amplitude device id is available + // if needed. + const createHandlersPromise = createHandlers(store); + const result = next(action); + + createHandlersPromise.then(handlers => { + initAnalytics(store, handlers); + }); + + return result; + } } const result = next(action); @@ -136,10 +151,6 @@ MiddlewareRegistry.register(store => next => action => { networkType: action.networkType })); break; - case SET_ROOM: { - initAnalytics(store); - break; - } case TRACK_ADDED: case TRACK_REMOVED: case TRACK_UPDATED: {