Source

services/logging-service.js

import { getCachedJWT } from '@/api/api-cache.js'
import api from '@/api'
import appService from '@/services/app-service.js'
import envService from '@/services/env-service.js'
import { errorLogger } from '@/plugins/error-logger.js'
import * as Sentry from '@sentry/browser'
import APIUrls from '@/config/simpliciti-apis.js'

const md5 = require('md5')

function isUserAuditEnabled() {
  return envService.getEnvValue('VUE_APP_DISABLE_USER_AUDIT', '0') !== '1'
}

/**
 * @namespace Services
 * @category Services
 * @module logging-service
 * */

//Hit third party provider once (to avoid API limits)
let browserIP = null
async function getBrowserIP() {
  if (browserIP) {
    return browserIP
  }
  try {
    let r = await fetch('https://api.ipify.org?format=json')
    r = await r.json()
    browserIP = r.ip
    return r.ip || ''
  } catch (err) {
    console.warn('Warning: Retrieving client IP', err.stack || err)
  }
}

/**
 * Will log a session event (auth required)
 * @function createGeoredClientUserSession
 */
export async function createGeoredClientUserSession(apiWrapperOptions = {}) {
  if (!isUserAuditEnabled()) {
    return false
  }
  //Try catch any errors until API fix (to be able to detect UniqueConstraintViolationException)
  try {
    let jwt = await getCachedJWT('createGeoredClientUserSession')
    if (jwt) {
      await api.v3.post(
        APIUrls.APIV3_USER_SESSIONS,
        {
          application: appService.getAppIdentifier(),
          sessionNumber: md5(jwt),
          userAgent: window.navigator.userAgent,
          ip: await getBrowserIP(),
          version: appService.getAppVersion(),
        },
        apiWrapperOptions
      )
    }
  } catch (err) {
    console.warn('createGeoredClientUserSession::catch', err.stack)
    //if (!err.stack.includes("409")) {
    //  throw err;
    //}
  }
  return true
}

/**
 * Will log an audit event (auth required)
 * @function createGeoredClientUserAudit
 */
export async function createEditGeoredClientUserAudit(
  functionId,
  payload = {},
  auditId = null
) {
  if (!isUserAuditEnabled()) {
    return false
  }
  let jwt = await getCachedJWT('createEditGeoredClientUserAudit')
  if (jwt) {
    let sessionNumber = md5(jwt)
    if (auditId) {
      return await api.v3.patch(`${APIUrls.APIV3_USER_AUDITS}/${auditId}`, {
        sessionNumber,
        functionId,
        ...payload,
      })
    } else {
      return await api.v3.post(
        `${APIUrls.APIV3_USER_AUDITS}?sessionNumber=${sessionNumber}`,
        {
          functionId,
          ...payload,
        }
      )
    }
  }
  return true
}

/**
 * @var {Object} scope default export object (internal)
 */
const scope = {
  isUserAuditEnabled,
  /**
   *
   * @param {String} payload.id User internal id
   * @returns
   */
  identifyUser(payload) {
    try {
      if (payload === null) {
        Sentry.configureScope((scope) => scope.setUser(null))
        return
      }
      Sentry.configureScope((scope) => scope.setUser(payload))
    } catch (err) {
      console.warn('logging: Fail to identify user')
      errorLogger.logError(err)
    }
  },
  setContext(contextName, payload = {}) {
    try {
      Sentry.setContext(contextName, payload)
    } catch (err) {
      console.warn('logging: Fail to set a context')
      errorLogger.logError(err)
    }
  },
  createSession: createGeoredClientUserSession,
  createAudit: createEditGeoredClientUserAudit,
  updateAudit: (auditId, functionId, payload) =>
    createEditGeoredClientUserAudit(functionId, payload, auditId),

  /**
   * Will createAudit/updateAudit for a functionId while computing duration (elapsed time)
   */
  activityAuditManager: {
    //Internal: Will save the activity start timestamp
    _startedActivities: {},
    hasActivityStarted(functionId) {
      return !!this._startedActivities[functionId]
    },
    /**
     * Will create audit given a functionId (will also stop any audit in progress for the same functionId)
     * @param {Number} functionId Module identifier
     */
    async startActivityAudit(functionId) {
      if (!isUserAuditEnabled()) {
        return false
      }

      this.stopAnyOtherActivityAudit(functionId)
      let response = await scope.createAudit(functionId, {
        duration: 0,
      })
      this._startedActivities[functionId] = {
        startDate: Date.now(),
        auditId: response.data.id,
      }
      return true
    },
    /**
     * Will update audit given a functionId (only if startActivityAudit has been called before)
     * @param {Number} functionId
     */
    stopActivityAudit(functionId) {
      if (!isUserAuditEnabled()) {
        return false
      }
      if (this.hasActivityStarted(functionId)) {
        //Hide error until API implemented (404)
        try {
          let { auditId, startDate } = this._startedActivities[functionId]
          scope.updateAudit(auditId, functionId, {
            duration: Math.round((Date.now() - startDate) / 1000),
          })
        } catch (err) {
          console.warn('Warning: Logging update audit', err.stack || err)
        }
        delete this._startedActivities[functionId]
        return true
      }
    },
    stopAnyOtherActivityAudit() {
      if (!isUserAuditEnabled()) {
        return false
      }
      Object.keys(this._startedActivities).forEach((functionId) => {
        this.stopActivityAudit(functionId)
      })
      return true
    },
  },
}
export default scope