| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- const SessionManager = require('../Authentication/SessionManager')
- const UserAnalyticsIdCache = require('./UserAnalyticsIdCache')
- const Settings = require('@overleaf/settings')
- const Metrics = require('../../infrastructure/Metrics')
- const Queues = require('../../infrastructure/Queues')
- const uuid = require('uuid')
- const _ = require('lodash')
- const { expressify } = require('../../util/promises')
- const { logger } = require('@overleaf/logger')
- const { getAnalyticsIdFromMongoUser } = require('./AnalyticsHelper')
- const analyticsEventsQueue = Queues.getQueue('analytics-events')
- const analyticsEditingSessionsQueue = Queues.getQueue(
- 'analytics-editing-sessions'
- )
- const analyticsUserPropertiesQueue = Queues.getQueue(
- 'analytics-user-properties'
- )
- const ONE_MINUTE_MS = 60 * 1000
- const UUID_REGEXP = /^[\w]{8}(-[\w]{4}){3}-[\w]{12}$/
- function identifyUser(userId, analyticsId, isNewUser) {
- if (!userId || !analyticsId || !analyticsId.toString().match(UUID_REGEXP)) {
- return
- }
- if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
- return
- }
- Metrics.analyticsQueue.inc({ status: 'adding', event_type: 'identify' })
- Queues.createScheduledJob(
- 'analytics-events',
- {
- name: 'identify',
- data: { userId, analyticsId, isNewUser, createdAt: new Date() },
- },
- ONE_MINUTE_MS
- )
- .then(() => {
- Metrics.analyticsQueue.inc({ status: 'added', event_type: 'identify' })
- })
- .catch(() => {
- Metrics.analyticsQueue.inc({ status: 'error', event_type: 'identify' })
- })
- }
- async function recordEventForUser(userId, event, segmentation) {
- if (!userId) {
- return
- }
- if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
- return
- }
- const analyticsId = await UserAnalyticsIdCache.get(userId)
- if (analyticsId) {
- _recordEvent({ analyticsId, userId, event, segmentation, isLoggedIn: true })
- }
- }
- function recordEventForSession(session, event, segmentation) {
- const { analyticsId, userId } = getIdsFromSession(session)
- if (!analyticsId) {
- return
- }
- if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
- return
- }
- logger.debug({
- analyticsId,
- userId,
- event,
- segmentation,
- isLoggedIn: !!userId,
- createdAt: new Date(),
- })
- _recordEvent({
- analyticsId,
- userId,
- event,
- segmentation,
- isLoggedIn: !!userId,
- createdAt: new Date(),
- })
- }
- async function setUserPropertyForUser(userId, propertyName, propertyValue) {
- if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
- return
- }
- _checkPropertyValue(propertyValue)
- const analyticsId = await UserAnalyticsIdCache.get(userId)
- if (analyticsId) {
- _setUserProperty({ analyticsId, propertyName, propertyValue })
- }
- }
- async function setUserPropertyForAnalyticsId(
- analyticsId,
- propertyName,
- propertyValue
- ) {
- if (_isAnalyticsDisabled()) {
- return
- }
- _checkPropertyValue(propertyValue)
- _setUserProperty({ analyticsId, propertyName, propertyValue })
- }
- async function setUserPropertyForSession(session, propertyName, propertyValue) {
- const { analyticsId, userId } = getIdsFromSession(session)
- if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
- return
- }
- _checkPropertyValue(propertyValue)
- if (analyticsId) {
- _setUserProperty({ analyticsId, propertyName, propertyValue })
- }
- }
- function updateEditingSession(userId, projectId, countryCode, segmentation) {
- if (!userId) {
- return
- }
- if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
- return
- }
- Metrics.analyticsQueue.inc({
- status: 'adding',
- event_type: 'editing-session',
- })
- analyticsEditingSessionsQueue
- .add('editing-session', {
- userId,
- projectId,
- countryCode,
- segmentation,
- createdAt: new Date(),
- })
- .then(() => {
- Metrics.analyticsQueue.inc({
- status: 'added',
- event_type: 'editing-session',
- })
- })
- .catch(() => {
- Metrics.analyticsQueue.inc({
- status: 'error',
- event_type: 'editing-session',
- })
- })
- }
- function _recordEvent(
- { analyticsId, userId, event, segmentation, isLoggedIn },
- { delay } = {}
- ) {
- Metrics.analyticsQueue.inc({ status: 'adding', event_type: 'event' })
- analyticsEventsQueue
- .add(
- 'event',
- {
- analyticsId,
- userId,
- event,
- segmentation,
- isLoggedIn,
- createdAt: new Date(),
- },
- { delay }
- )
- .then(() => {
- Metrics.analyticsQueue.inc({ status: 'added', event_type: 'event' })
- })
- .catch(() => {
- Metrics.analyticsQueue.inc({ status: 'error', event_type: 'event' })
- })
- }
- function _setUserProperty({ analyticsId, propertyName, propertyValue }) {
- Metrics.analyticsQueue.inc({
- status: 'adding',
- event_type: 'user-property',
- })
- analyticsUserPropertiesQueue
- .add('user-property', {
- analyticsId,
- propertyName,
- propertyValue,
- createdAt: new Date(),
- })
- .then(() => {
- Metrics.analyticsQueue.inc({
- status: 'added',
- event_type: 'user-property',
- })
- })
- .catch(() => {
- Metrics.analyticsQueue.inc({
- status: 'error',
- event_type: 'user-property',
- })
- })
- }
- function _isSmokeTestUser(userId) {
- const smokeTestUserId = Settings.smokeTest && Settings.smokeTest.userId
- return (
- smokeTestUserId != null &&
- userId != null &&
- userId.toString() === smokeTestUserId
- )
- }
- function _isAnalyticsDisabled() {
- return !(Settings.analytics && Settings.analytics.enabled)
- }
- function _checkPropertyValue(propertyValue) {
- if (propertyValue === undefined) {
- throw new Error(
- 'propertyValue cannot be undefined, use null to unset a property'
- )
- }
- }
- function getIdsFromSession(session) {
- const analyticsId = _.get(session, ['analyticsId'])
- const userId = SessionManager.getLoggedInUserId(session)
- return { analyticsId, userId }
- }
- async function analyticsIdMiddleware(req, res, next) {
- const session = req.session
- const sessionUser = SessionManager.getSessionUser(session)
- if (sessionUser) {
- session.analyticsId = getAnalyticsIdFromMongoUser(sessionUser)
- } else if (!session.analyticsId) {
- // generate an `analyticsId` if needed
- session.analyticsId = uuid.v4()
- }
- next()
- }
- module.exports = {
- identifyUser,
- recordEventForSession,
- recordEventForUser,
- setUserPropertyForUser,
- setUserPropertyForSession,
- setUserPropertyForAnalyticsId,
- updateEditingSession,
- getIdsFromSession,
- analyticsIdMiddleware: expressify(analyticsIdMiddleware),
- }
|