import { Reporter } from './reporter' ; import { createApplicationUsageMetric , ApplicationUsageMetric } from './metrics' ; type TrackedApplication = Record < string , ApplicationUsageMetric > ; interface ApplicationKey { appId : string ; viewId : string ; } export class ApplicationUsageTracker { private trackedApplicationViews : TrackedApplication = { } ; private reporter : Reporter ; private currentAppId ? : string ; private currentApplicationKeys : ApplicationKey [ ] = [ ] ; private beforeUnloadListener ? : EventListener ; private onVisiblityChangeListener ? : EventListener ; constructor ( reporter ) { this . reporter = reporter ; } private createKey ( appId , viewId ) { return { appId , viewId } ; } static serializeKey ( { appId , viewId } ) { return `${ appId }-${ viewId }` ; } private trackApplications ( appKeys ) { for ( const { appId , viewId } of appKeys . filter ( Boolean ) ) { const serializedKey = ApplicationUsageTracker . serializeKey ( { appId , viewId } ) ; if ( typeof this . trackedApplicationViews [ serializedKey ] !== 'undefined' ) { continue ; } const metric = createApplicationUsageMetric ( appId , viewId ) ; this . trackedApplicationViews [ serializedKey ] = metric ; } } private attachListeners ( ) { if ( typeof window === 'undefined' || typeof document === 'undefined' ) { return ; } this . beforeUnloadListener = ( ) => { this . flushTrackedViews ( ) ; } ; this . onVisiblityChangeListener = ( ) => { if ( document . visibilityState === 'visible' ) { this . resumeTrackingAll ( ) ; } else if ( document . visibilityState === 'hidden' ) { this . pauseTrackingAll ( ) ; } } ; window . addEventListener ( 'beforeunload' , this . beforeUnloadListener ) ; document . addEventListener ( 'visibilitychange' , this . onVisiblityChangeListener ) ; } private detachListeners ( ) { if ( typeof window === 'undefined' || typeof document === 'undefined' ) { return ; } if ( this . beforeUnloadListener ) { window . removeEventListener ( 'beforeunload' , this . beforeUnloadListener ) ; } if ( this . onVisiblityChangeListener ) { document . removeEventListener ( 'visibilitychange' , this . onVisiblityChangeListener ) ; } } private sendMetricsToReporter ( metrics ) { metrics . forEach ( ( metric ) => { this . reporter . reportApplicationUsage ( metric ) ; } ) ; } public updateViewClickCounter ( viewId ) { if ( ! this . currentAppId ) { return ; } const appKey = ApplicationUsageTracker . serializeKey ( { appId : this . currentAppId , viewId } ) ; if ( this . trackedApplicationViews [ appKey ] ) { this . trackedApplicationViews [ appKey ] . numberOfClicks ++ ; } } private flushTrackedViews ( ) { const appViewMetrics = Object . values ( this . trackedApplicationViews ) ; this . sendMetricsToReporter ( appViewMetrics ) ; this . trackedApplicationViews = { } ; } public start ( ) { this . attachListeners ( ) ; } public stop ( ) { this . flushTrackedViews ( ) ; this . detachListeners ( ) ; } public setCurrentAppId ( appId ) { this . flushTrackedViews ( ) ; this . currentAppId = appId ; } public trackApplicationViewUsage ( viewId ) { if ( ! this . currentAppId ) { return ; } const appKey = this . createKey ( this . currentAppId , viewId ) ; this . trackApplications ( [ appKey ] ) ; } public pauseTrackingAll ( ) { this . currentApplicationKeys = Object . values ( this . trackedApplicationViews ) . map ( ( { appId , viewId } ) => this . createKey ( appId , viewId ) ) ; this . flushTrackedViews ( ) ; } public resumeTrackingAll ( ) { this . trackApplications ( this . currentApplicationKeys ) ; this . currentApplicationKeys = [ ] ; this . reporter . sendReports ( ) ; } public flushTrackedView ( viewId ) { if ( ! this . currentAppId ) { return ; } const appKey = this . createKey ( this . currentAppId , viewId ) ; const serializedKey = ApplicationUsageTracker . serializeKey ( appKey ) ; const appViewMetric = this . trackedApplicationViews [ serializedKey ] ; if ( appViewMetric ) { this . sendMetricsToReporter ( [ appViewMetric ] ) ; delete this . trackedApplicationViews [ serializedKey ] ; } } }