2017-10-04 22:36:09 +00:00
|
|
|
// @flow
|
|
|
|
|
2020-06-16 14:35:12 +00:00
|
|
|
import { API_ID } from '../../../modules/API';
|
2020-05-20 10:57:03 +00:00
|
|
|
import {
|
|
|
|
checkChromeExtensionsInstalled,
|
|
|
|
isMobileBrowser
|
|
|
|
} from '../base/environment/utils';
|
2017-10-10 23:43:22 +00:00
|
|
|
import JitsiMeetJS, {
|
|
|
|
analytics,
|
2020-02-25 12:41:13 +00:00
|
|
|
browser,
|
2017-10-10 23:43:22 +00:00
|
|
|
isAnalyticsEnabled
|
|
|
|
} from '../base/lib-jitsi-meet';
|
2017-09-01 19:14:03 +00:00
|
|
|
import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
|
2020-05-20 10:57:03 +00:00
|
|
|
|
2020-04-29 10:40:04 +00:00
|
|
|
import { AmplitudeHandler, MatomoHandler } from './handlers';
|
2019-08-21 14:50:00 +00:00
|
|
|
import logger from './logger';
|
2017-09-01 19:14:03 +00:00
|
|
|
|
2017-10-20 19:30:43 +00:00
|
|
|
/**
|
2018-01-03 21:24:07 +00:00
|
|
|
* Sends an event through the lib-jitsi-meet AnalyticsAdapter interface.
|
2017-10-20 19:30:43 +00:00
|
|
|
*
|
2018-01-03 21:24:07 +00:00
|
|
|
* @param {Object} event - The event to send. It should be formatted as
|
|
|
|
* described in AnalyticsAdapter.js in lib-jitsi-meet.
|
|
|
|
* @returns {void}
|
2017-10-20 19:30:43 +00:00
|
|
|
*/
|
2018-01-03 21:24:07 +00:00
|
|
|
export function sendAnalytics(event: Object) {
|
2018-01-17 15:58:21 +00:00
|
|
|
try {
|
|
|
|
analytics.sendEvent(event);
|
|
|
|
} catch (e) {
|
|
|
|
logger.warn(`Error sending analytics event: ${e}`);
|
|
|
|
}
|
2017-10-20 19:30:43 +00:00
|
|
|
}
|
2017-10-10 23:43:22 +00:00
|
|
|
|
2019-01-24 15:37:55 +00:00
|
|
|
/**
|
|
|
|
* Resets the analytics adapter to its initial state - removes handlers, cache,
|
|
|
|
* disabled state, etc.
|
|
|
|
*
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
export function resetAnalytics() {
|
|
|
|
analytics.reset();
|
|
|
|
}
|
|
|
|
|
2017-09-01 19:14:03 +00:00
|
|
|
/**
|
2020-01-30 16:57:41 +00:00
|
|
|
* Creates the analytics handlers.
|
2017-09-01 19:14:03 +00:00
|
|
|
*
|
2020-01-30 16:57:41 +00:00
|
|
|
* @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.
|
2017-09-01 19:14:03 +00:00
|
|
|
*/
|
2020-01-30 16:57:41 +00:00
|
|
|
export function createHandlers({ getState }: { getState: Function }) {
|
2017-09-01 19:14:03 +00:00
|
|
|
getJitsiMeetGlobalNS().analyticsHandlers = [];
|
2017-09-25 17:31:19 +00:00
|
|
|
window.analyticsHandlers = []; // Legacy support.
|
2017-09-01 19:14:03 +00:00
|
|
|
|
2020-01-30 16:57:41 +00:00
|
|
|
if (!isAnalyticsEnabled(getState)) {
|
|
|
|
return Promise.resolve([]);
|
2017-09-01 19:14:03 +00:00
|
|
|
}
|
|
|
|
|
2017-11-29 02:57:13 +00:00
|
|
|
const state = getState();
|
|
|
|
const config = state['features/base/config'];
|
2019-01-24 15:37:55 +00:00
|
|
|
const { locationURL } = state['features/base/connection'];
|
|
|
|
const host = locationURL ? locationURL.host : '';
|
2019-01-03 14:48:41 +00:00
|
|
|
const {
|
|
|
|
analytics: analyticsConfig = {},
|
|
|
|
deploymentInfo
|
|
|
|
} = config;
|
2019-01-03 13:54:02 +00:00
|
|
|
const {
|
|
|
|
amplitudeAPPKey,
|
2019-08-30 15:16:05 +00:00
|
|
|
blackListedEvents,
|
2019-01-03 14:48:41 +00:00
|
|
|
scriptURLs,
|
2019-08-30 15:16:05 +00:00
|
|
|
googleAnalyticsTrackingId,
|
2020-04-28 11:23:04 +00:00
|
|
|
matomoEndpoint,
|
|
|
|
matomoSiteID,
|
2019-08-30 15:16:05 +00:00
|
|
|
whiteListedEvents
|
2019-01-03 14:48:41 +00:00
|
|
|
} = analyticsConfig;
|
2020-01-30 16:57:41 +00:00
|
|
|
const { group, user } = state['features/base/jwt'];
|
2017-09-01 19:14:03 +00:00
|
|
|
const handlerConstructorOptions = {
|
2019-01-03 13:54:02 +00:00
|
|
|
amplitudeAPPKey,
|
2019-08-30 15:16:05 +00:00
|
|
|
blackListedEvents,
|
2018-01-04 15:34:12 +00:00
|
|
|
envType: (deploymentInfo && deploymentInfo.envType) || 'dev',
|
2018-02-12 19:36:10 +00:00
|
|
|
googleAnalyticsTrackingId,
|
2020-04-28 11:23:04 +00:00
|
|
|
matomoEndpoint,
|
|
|
|
matomoSiteID,
|
2018-01-04 15:11:35 +00:00
|
|
|
group,
|
2019-01-24 15:37:55 +00:00
|
|
|
host,
|
2018-01-23 22:14:31 +00:00
|
|
|
product: deploymentInfo && deploymentInfo.product,
|
2018-01-04 15:34:12 +00:00
|
|
|
subproduct: deploymentInfo && deploymentInfo.environment,
|
2018-01-08 17:27:08 +00:00
|
|
|
user: user && user.id,
|
2019-08-30 15:16:05 +00:00
|
|
|
version: JitsiMeetJS.version,
|
|
|
|
whiteListedEvents
|
2017-09-01 19:14:03 +00:00
|
|
|
};
|
2020-01-30 16:57:41 +00:00
|
|
|
const handlers = [];
|
2017-09-01 19:14:03 +00:00
|
|
|
|
2020-01-30 16:57:41 +00:00
|
|
|
try {
|
|
|
|
const amplitude = new AmplitudeHandler(handlerConstructorOptions);
|
|
|
|
|
|
|
|
handlers.push(amplitude);
|
|
|
|
// eslint-disable-next-line no-empty
|
|
|
|
} catch (e) {}
|
|
|
|
|
2020-04-29 10:40:04 +00:00
|
|
|
try {
|
|
|
|
const matomo = new MatomoHandler(handlerConstructorOptions);
|
|
|
|
|
|
|
|
handlers.push(matomo);
|
|
|
|
// eslint-disable-next-line no-empty
|
|
|
|
} catch (e) {}
|
|
|
|
|
2020-01-30 16:57:41 +00:00
|
|
|
return (
|
|
|
|
_loadHandlers(scriptURLs, handlerConstructorOptions)
|
|
|
|
.then(externalHandlers => {
|
|
|
|
handlers.push(...externalHandlers);
|
|
|
|
if (handlers.length === 0) {
|
2020-04-28 11:23:04 +00:00
|
|
|
// Throwing an error in order to dispose the analytics in the catch clause due to the lack of any
|
2020-01-30 16:57:41 +00:00
|
|
|
// analytics handlers.
|
|
|
|
throw new Error('No analytics handlers created!');
|
|
|
|
}
|
2017-09-01 19:14:03 +00:00
|
|
|
|
2020-01-30 16:57:41 +00:00
|
|
|
return handlers;
|
|
|
|
})
|
|
|
|
.catch(e => {
|
|
|
|
analytics.dispose();
|
|
|
|
logger.error(e);
|
2017-09-01 19:14:03 +00:00
|
|
|
|
2020-01-30 16:57:41 +00:00
|
|
|
return [];
|
|
|
|
}));
|
2020-01-24 20:43:42 +00:00
|
|
|
|
2020-01-30 16:57:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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<Object>} handlers - The analytics handlers.
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
|
|
|
export function initAnalytics({ getState }: { getState: Function }, handlers: Array<Object>) {
|
|
|
|
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';
|
|
|
|
|
2020-06-16 14:35:12 +00:00
|
|
|
// permanentProperties is external api
|
|
|
|
permanentProperties.externalApi = typeof API_ID === 'number';
|
|
|
|
|
|
|
|
// Report if we are loaded in iframe
|
|
|
|
permanentProperties.inIframe = _inIframe();
|
|
|
|
|
2020-01-30 16:57:41 +00:00
|
|
|
// 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];
|
2017-09-01 19:14:03 +00:00
|
|
|
}
|
2020-01-30 16:57:41 +00:00
|
|
|
}
|
|
|
|
}
|
2017-09-01 19:14:03 +00:00
|
|
|
|
2020-01-30 16:57:41 +00:00
|
|
|
analytics.addPermanentProperties(permanentProperties);
|
|
|
|
analytics.setConferenceName(roomName);
|
2018-01-03 21:24:07 +00:00
|
|
|
|
2020-01-30 16:57:41 +00:00
|
|
|
// Set the handlers last, since this triggers emptying of the cache
|
|
|
|
analytics.setAnalyticsHandlers(handlers);
|
2020-02-25 12:41:13 +00:00
|
|
|
|
|
|
|
if (!isMobileBrowser() && browser.isChrome()) {
|
|
|
|
const bannerCfg = state['features/base/config'].chromeExtensionBanner;
|
|
|
|
|
|
|
|
checkChromeExtensionsInstalled(bannerCfg).then(extensionsInstalled => {
|
|
|
|
if (extensionsInstalled?.length) {
|
|
|
|
analytics.addPermanentProperties({
|
|
|
|
hasChromeExtension: extensionsInstalled.some(ext => ext)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2017-09-01 19:14:03 +00:00
|
|
|
}
|
|
|
|
|
2020-06-16 14:35:12 +00:00
|
|
|
/**
|
|
|
|
* Checks whether we are loaded in iframe.
|
|
|
|
*
|
|
|
|
* @returns {boolean} Returns {@code true} if loaded in iframe.
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
function _inIframe() {
|
|
|
|
if (navigator.product === 'ReactNative') {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
return window.self !== window.top;
|
|
|
|
} catch (e) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-01 19:14:03 +00:00
|
|
|
/**
|
2020-01-30 16:57:41 +00:00
|
|
|
* Tries to load the scripts for the analytics handlers and creates them.
|
2017-09-01 19:14:03 +00:00
|
|
|
*
|
|
|
|
* @param {Array} scriptURLs - The array of script urls to load.
|
2020-01-30 16:57:41 +00:00
|
|
|
* @param {Object} handlerConstructorOptions - The default options to pass when creating handlers.
|
2017-09-01 19:14:03 +00:00
|
|
|
* @private
|
2020-01-30 16:57:41 +00:00
|
|
|
* @returns {Promise} Resolves with the handlers that have been successfully loaded and rejects if there are no handlers
|
|
|
|
* loaded or the analytics is disabled.
|
2017-09-01 19:14:03 +00:00
|
|
|
*/
|
2019-01-24 15:37:55 +00:00
|
|
|
function _loadHandlers(scriptURLs = [], handlerConstructorOptions) {
|
2017-09-01 19:14:03 +00:00
|
|
|
const promises = [];
|
|
|
|
|
|
|
|
for (const url of scriptURLs) {
|
|
|
|
promises.push(
|
|
|
|
loadScript(url).then(
|
|
|
|
() => {
|
|
|
|
return { type: 'success' };
|
|
|
|
},
|
|
|
|
error => {
|
|
|
|
return {
|
|
|
|
type: 'error',
|
|
|
|
error,
|
|
|
|
url
|
|
|
|
};
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.all(promises).then(values => {
|
|
|
|
for (const el of values) {
|
|
|
|
if (el.type === 'error') {
|
|
|
|
logger.warn(`Failed to load ${el.url}: ${el.error}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// analyticsHandlers is the handlers we want to use
|
|
|
|
// we search for them in the JitsiMeetGlobalNS, but also
|
|
|
|
// check the old location to provide legacy support
|
2017-09-04 09:35:09 +00:00
|
|
|
const analyticsHandlers = [
|
|
|
|
...getJitsiMeetGlobalNS().analyticsHandlers,
|
2020-01-30 16:57:41 +00:00
|
|
|
...window.analyticsHandlers
|
2019-01-24 15:37:55 +00:00
|
|
|
];
|
2017-10-04 22:36:09 +00:00
|
|
|
const handlers = [];
|
2017-09-01 19:14:03 +00:00
|
|
|
|
2017-10-04 22:36:09 +00:00
|
|
|
for (const Handler of analyticsHandlers) {
|
|
|
|
// Catch any error while loading to avoid skipping analytics in case
|
|
|
|
// of multiple scripts.
|
|
|
|
try {
|
|
|
|
handlers.push(new Handler(handlerConstructorOptions));
|
|
|
|
} catch (error) {
|
|
|
|
logger.warn(`Error creating analytics handler: ${error}`);
|
|
|
|
}
|
2017-09-01 19:14:03 +00:00
|
|
|
}
|
2017-10-04 22:36:09 +00:00
|
|
|
logger.debug(`Loaded ${handlers.length} analytics handlers`);
|
|
|
|
|
|
|
|
return handlers;
|
2017-09-01 19:14:03 +00:00
|
|
|
});
|
|
|
|
}
|