ref(Amplitude): device id syncing

This commit is contained in:
Hristo Terezov 2020-01-30 16:57:41 +00:00 committed by Дамян Минков
parent 7d67cb583e
commit b64260e554
8 changed files with 122 additions and 95 deletions

View File

@ -35,28 +35,23 @@ export function resetAnalytics() {
} }
/** /**
* Loads the analytics scripts and inits JitsiMeetJS.analytics by setting * Creates the analytics handlers.
* 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} * @param {Store} store - The redux store in which the specified {@code action} is being dispatched.
* is being dispatched. * @returns {Promise} Resolves with the handlers that have been successfully loaded.
* @returns {void}
*/ */
export function initAnalytics({ getState }: { getState: Function }) { export function createHandlers({ getState }: { getState: Function }) {
getJitsiMeetGlobalNS().analyticsHandlers = []; getJitsiMeetGlobalNS().analyticsHandlers = [];
window.analyticsHandlers = []; // Legacy support. window.analyticsHandlers = []; // Legacy support.
if (!analytics || !isAnalyticsEnabled(getState)) { if (!isAnalyticsEnabled(getState)) {
return; return Promise.resolve([]);
} }
const state = getState(); const state = getState();
const config = state['features/base/config']; const config = state['features/base/config'];
const { locationURL } = state['features/base/connection']; const { locationURL } = state['features/base/connection'];
const host = locationURL ? locationURL.host : ''; const host = locationURL ? locationURL.host : '';
const { const {
analytics: analyticsConfig = {}, analytics: analyticsConfig = {},
deploymentInfo deploymentInfo
@ -68,7 +63,7 @@ export function initAnalytics({ getState }: { getState: Function }) {
googleAnalyticsTrackingId, googleAnalyticsTrackingId,
whiteListedEvents whiteListedEvents
} = analyticsConfig; } = analyticsConfig;
const { group, server, user } = state['features/base/jwt']; const { group, user } = state['features/base/jwt'];
const handlerConstructorOptions = { const handlerConstructorOptions = {
amplitudeAPPKey, amplitudeAPPKey,
blackListedEvents, blackListedEvents,
@ -82,54 +77,93 @@ export function initAnalytics({ getState }: { getState: Function }) {
version: JitsiMeetJS.version, version: JitsiMeetJS.version,
whiteListedEvents whiteListedEvents
}; };
const handlers = [];
_loadHandlers(scriptURLs, handlerConstructorOptions) try {
.then(handlers => { const amplitude = new AmplitudeHandler(handlerConstructorOptions);
const roomName = state['features/base/conference'].room;
const permanentProperties = {};
if (server) { handlers.push(amplitude);
permanentProperties.server = server; // eslint-disable-next-line no-empty
} } catch (e) {}
if (group) {
permanentProperties.group = group;
}
// Report if user is using websocket return (
permanentProperties.websocket = navigator.product !== 'ReactNative' && typeof config.websocket === 'string'; _loadHandlers(scriptURLs, handlerConstructorOptions)
.then(externalHandlers => {
// Optionally, include local deployment information based on the handlers.push(...externalHandlers);
// contents of window.config.deploymentInfo. if (handlers.length === 0) {
if (deploymentInfo) { // Throwing an error in order to dispose the analytics in the catch clause due to the lack of any
for (const key in deploymentInfo) { // analytics handlers.
if (deploymentInfo.hasOwnProperty(key)) { throw new Error('No analytics handlers created!');
permanentProperties[key] = deploymentInfo[key];
}
} }
}
analytics.addPermanentProperties(permanentProperties); return handlers;
analytics.setConferenceName(roomName); })
.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<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';
// 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 {Array} scriptURLs - The array of script urls to load.
* @param {Object} handlerConstructorOptions - The default options to pass when * @param {Object} handlerConstructorOptions - The default options to pass when creating handlers.
* creating handlers.
* @private * @private
* @returns {Promise} Resolves with the handlers that have been * @returns {Promise} Resolves with the handlers that have been successfully loaded and rejects if there are no handlers
* successfully loaded and rejects if there are no handlers loaded or the * loaded or the analytics is disabled.
* analytics is disabled.
*/ */
function _loadHandlers(scriptURLs = [], handlerConstructorOptions) { function _loadHandlers(scriptURLs = [], handlerConstructorOptions) {
const promises = []; const promises = [];
@ -161,13 +195,8 @@ function _loadHandlers(scriptURLs = [], handlerConstructorOptions) {
// check the old location to provide legacy support // check the old location to provide legacy support
const analyticsHandlers = [ const analyticsHandlers = [
...getJitsiMeetGlobalNS().analyticsHandlers, ...getJitsiMeetGlobalNS().analyticsHandlers,
...window.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
]; ];
const handlers = []; const handlers = [];
for (const Handler of analyticsHandlers) { for (const Handler of analyticsHandlers) {
@ -179,7 +208,6 @@ function _loadHandlers(scriptURLs = [], handlerConstructorOptions) {
logger.warn(`Error creating analytics handler: ${error}`); logger.warn(`Error creating analytics handler: ${error}`);
} }
} }
logger.debug(`Loaded ${handlers.length} analytics handlers`); logger.debug(`Loaded ${handlers.length} analytics handlers`);
return handlers; return handlers;

View File

@ -1,5 +1,5 @@
import AbstractHandler from './AbstractHandler'; import AbstractHandler from './AbstractHandler';
import { amplitude } from './amplitude'; import { amplitude, fixDeviceID } from './amplitude';
/** /**
* Analytics handler for 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.getInstance(this._amplitudeOptions).init(amplitudeAPPKey, undefined, { includeReferrer: true });
amplitude.fixDeviceID(this._amplitudeOptions); fixDeviceID(amplitude.getInstance(this._amplitudeOptions));
if (user) { if (user) {
amplitude.getInstance(this._amplitudeOptions).setUserId(user); amplitude.getInstance(this._amplitudeOptions).setUserId(user);

View File

@ -111,12 +111,5 @@ export default {
} }
return instance; return instance;
}, }
/**
* Currently not implemented.
*
* @returns {void}
*/
fixDeviceID() { } // eslint-disable-line no-empty-function
}; };

View File

@ -10,29 +10,5 @@ export default {
*/ */
getInstance(options = {}) { getInstance(options = {}) {
return amplitude.getInstance(options.instanceName); 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);
}
} }
}; };

View File

@ -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
}

View File

@ -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
}

View File

@ -1 +1,2 @@
export { default as amplitude } from './Amplitude'; export { default as amplitude } from './Amplitude';
export * from './fixDeviceID';

View File

@ -18,7 +18,7 @@ import {
import { UPDATE_LOCAL_TRACKS_DURATION } from './actionTypes'; import { UPDATE_LOCAL_TRACKS_DURATION } from './actionTypes';
import { createLocalTracksDurationEvent, createNetworkInfoEvent } from './AnalyticsEvents'; 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. * Calculates the duration of the local tracks.
@ -79,13 +79,28 @@ function calculateLocalTrackDuration(state) {
* @returns {Function} * @returns {Function}
*/ */
MiddlewareRegistry.register(store => next => action => { MiddlewareRegistry.register(store => next => action => {
if (action.type === SET_CONFIG) { switch (action.type) {
case SET_CONFIG:
if (navigator.product === 'ReactNative') { if (navigator.product === 'ReactNative') {
// Reseting the analytics is currently not needed for web because // Reseting the analytics is currently not needed for web because
// the user will be redirected to another page and new instance of // the user will be redirected to another page and new instance of
// Analytics will be created and initialized. // Analytics will be created and initialized.
resetAnalytics(); 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); const result = next(action);
@ -136,10 +151,6 @@ MiddlewareRegistry.register(store => next => action => {
networkType: action.networkType networkType: action.networkType
})); }));
break; break;
case SET_ROOM: {
initAnalytics(store);
break;
}
case TRACK_ADDED: case TRACK_ADDED:
case TRACK_REMOVED: case TRACK_REMOVED:
case TRACK_UPDATED: { case TRACK_UPDATED: {