import ua , { EventParams } from 'universal-analytics' ; import { Logger } from 'polar-shared/src/logger/Logger' ; import { CIDs } from './CIDs' ; import { Stopwatch , TrackedDuration } from 'polar-shared/src/util/Stopwatch' ; import { Stopwatches } from 'polar-shared/src/util/Stopwatches' ; import { DurationMS } from 'polar-shared/src/util/TimeDurations' ; import { Analytics } from "../analytics/Analytics" ; const TRACKING_ID = 'UA-122721184-5' ; const DEBUG = false ; declare var window ; const isBrowserContext = typeof window !== 'undefined' ; const cid = isBrowserContext ? CIDs . get ( ) : 'none' ; const headers = { } ; const visitorOptions = { cid , headers } ; const visitor = ua ( TRACKING_ID , visitorOptions ) . debug ( DEBUG ) ; const log = Logger . create ( ) ; const defaultCallback = ( err , response , body ) => { if ( err ) { log . warn ( "Unable to track analytics: " , err ) ; } } ; export class RendererAnalytics { public static event ( args ) { if ( ! isBrowserContext ) { return ; } const callback = defaultCallback ; const eventParams = { ec : args . category , ea : args . action , } ; visitor . event ( eventParams ) . send ( callback ) ; } /**
     * https://support.google.com/analytics/answer/3123666?hl=en
     *
     * https://support.google.com/analytics/answer/3123662?hl=en&utm_id=ad
     *
     * You must make sure you have the full rights to use this service, to upload data, and to use it with your Google
     * Analytics account.
     *
     * You will give your end users proper notice about the implementations and features of Google Analytics you use
     * (e.g. notice about what data you will collect via Google Analytics, and whether this data can be connected to
     * other data you have about the end user). You will either get consent from your end users, or provide them with
     * the opportunity to opt-out from the implementations and features you use.
     *
     * You will not upload any data that allows Google to personally identify an individual (such as certain names,
     * social security numbers, email addresses, or any similar data), or data that permanently identifies a particular
     * device (such as a mobile phone’s unique device identifier if such an identifier cannot be reset).
     *
     * If you upload any data that allows Google to personally identify an individual, your Google Analytics account can
     * be terminated, and you may lose your Google Analytics data.
     *
     * You will only session stitch authenticated and unauthenticated sessions of your end users if your end users have
     * given consent to such stitch, or if such merger is allowed under applicable laws and regulations.
     *
     * ga('set', 'userId', 'USER_ID');
     *
     * Set the user ID using signed-in user_id.
     *
     */ public static identify ( uid ) { visitor . set ( 'userId' , uid ) ; } public static pageview ( path , hostname ? , title ? ) { if ( ! isBrowserContext ) { log . warn ( "Not called from browser context" ) ; return ; } const callback = defaultCallback ; const pageviewParams = { dp : path , dh : hostname , dt : title , } ; visitor . pageview ( pageviewParams ) . send ( callback ) ; } public static timing ( category , variable , time ) { if ( ! isBrowserContext ) { log . warn ( "Not called from browser context" ) ; return ; } const callback = defaultCallback ; visitor . timing ( category , variable , time ) . send ( callback ) ; } public static createTimer ( category , variable ) { return new DefaultTimer ( category , variable ) ; } /**
     * Perform the operation with a timer to record the duration.
     */ public static withTimer < T > ( category , variable , closure ) { const stopwatch = this . createTimer ( category , variable ) ; try { const result = closure ( ) ; return result ; } finally { stopwatch . stop ( ) ; } } /**
     * Perform the operation with a stopwatch to record the duration.
     */ public static async withTimerAsync < T > ( category , variable , closure ) { const stopwatch = this . createTimer ( category , variable ) ; try { const result = await closure ( ) ; return result ; } finally { stopwatch . stop ( ) ; } } public static createTracer ( category ) { return new DefaultTracer ( category ) ; } public static set ( fieldsObject ) { for ( const key of Object . keys ( fieldsObject ) ) { const value = fieldsObject [ key ] ; visitor . set ( key , value ) ; } } } export interface IEventArgs { category : string ; action : string ; } export interface IFieldsObject { [ i ] : any ; } export interface Timer { /**
     * Stop the timer and record the amount of time it took to perform the
     * operation.
     */ stop ( ) : void ; } class LogThresholds { public static readonly info = 500 ; public static readonly warn = 750 ; public static readonly error = 1500 ; } class DefaultTimer implements Timer { private stopped : boolean = false ; constructor ( private readonly category , private readonly variable , private readonly stopwatch = Stopwatches . create ( ) ) { } public stop ( ) { if ( this . stopped ) { log . warn ( "Stop called twice" ) ; return ; } const duration = this . stopwatch . stop ( ) ; this . doAnalytics ( duration ) ; this . doLogging ( duration ) ; this . stopped = true ; } private doAnalytics ( duration ) { if ( navigator . onLine ) { RendererAnalytics . timing ( this . category , this . variable , duration . durationMS ) ; } } private doLogging ( duration ) { type ThresholdLevel = 'error' | 'warn' | 'info' ; const toLevel = ( duration ) : ThresholdLevel | undefined => { if ( duration > LogThresholds . error ) { return 'error' ; } else if ( duration > LogThresholds . warn ) { return 'warn' ; } else if ( duration > LogThresholds . info ) { return 'info' ; } return undefined ; } ; } } /**
 * Tracer object which is similar than using a timer since we don't constantly
 * specify a category.
 */ export interface Tracer { trace < T > ( variable , closure ) : T ; traceAsync < T > ( variable , closure ) : Promise < T > ; } class DefaultTracer implements Tracer { constructor ( private readonly category ) { } public trace < T > ( variable , closure ) { return RendererAnalytics . withTimer ( this . category , variable , closure ) ; } public async traceAsync < T > ( variable , closure ) { return await RendererAnalytics . withTimerAsync ( this . category , variable , closure ) ; } } export class NullTracer implements Tracer { public trace < T > ( variable , closure ) { return closure ( ) ; } public async traceAsync < T > ( variable , closure ) { return await closure ( ) ; } }