import * as appInsights from "applicationinsights" ; import * as readLine from "readline-sync" ; import * as jsonData from "./usageDataSettings" ; import * as defaults from "./defaults" ; enum Application export enum UsageDataReportingMethod { applicationInsights = "applicationInsights" } enum off enum on export enum UsageDataLevel { off = "off" , on = "on" } member export class ExpectedError extends Error { constructor ( message ) { super ( message ) ; Object . setPrototypeOf ( this , ExpectedError . prototype ) ; } } member member member member member member member member export interface IUsageDataOptions { groupName ? : string ; projectName : string ; instrumentationKey : string ; promptQuestion ? : string ; raisePrompt ? : boolean ; usageDataLevel ? : UsageDataLevel ; method ? : UsageDataReportingMethod ; isForTesting ? : boolean ; } param usageDataObject export class OfficeAddinUsageData { private usageDataClient = appInsights . defaultClient ; private eventsSent : number = 0 ; private exceptionsSent : number = 0 ; private options : IUsageDataOptions ; private defaultData = { Platform : process . platform , NodeVersion : process . version , } ; constructor ( usageDataOptions ) { try { this . options = { groupName : defaults . groupName , promptQuestion : "" , raisePrompt : true , usageDataLevel : UsageDataLevel . off , method : UsageDataReportingMethod . applicationInsights , isForTesting : false , ... usageDataOptions , } ; if ( this . options . instrumentationKey === undefined ) { throw new Error ( "Instrumentation Key not defined - cannot create usage data object" ) ; } if ( this . options . groupName === undefined ) { throw new Error ( "Group Name not defined - cannot create usage data object" ) ; } if ( jsonData . groupNameExists ( this . options . groupName ) ) { this . options . usageDataLevel = jsonData . readUsageDataLevel ( this . options . groupName ) ; } if ( this . options . projectName === defaults . generatorOffice && this . options . instrumentationKey === defaults . instrumentationKeyForOfficeAddinCLITools && jsonData . needToPromptForUsageData ( this . options . groupName ) ) { jsonData . writeUsageDataJsonData ( this . options . groupName , this . options . usageDataLevel ) ; } if ( ! this . options . isForTesting && this . options . raisePrompt && jsonData . needToPromptForUsageData ( this . options . groupName ) ) { this . usageDataOptIn ( ) ; } if ( this . options . usageDataLevel === UsageDataLevel . on ) { appInsights . setup ( this . options . instrumentationKey ) . setAutoCollectExceptions ( false ) . start ( ) ; this . usageDataClient = appInsights . defaultClient ; this . removeApplicationInsightsSensitiveInformation ( ) ; } } catch ( err ) { throw new Error ( err ) ; } } param eventName param data public async reportEvent ( eventName , data ) { if ( this . getUsageDataLevel ( ) === UsageDataLevel . on ) { this . reportEventApplicationInsights ( eventName , data ) ; } } param eventName param data public async reportEventApplicationInsights ( eventName , data ) { if ( this . getUsageDataLevel ( ) === UsageDataLevel . on ) { const usageDataEvent = new appInsights . Contracts . EventData ( ) ; usageDataEvent . name = this . options . isForTesting ? `${ eventName }-test` : eventName ; try { for ( const [ key , [ value , elapsedTime ] ] of Object . entries ( data ) ) { usageDataEvent . properties [ key ] = value ; usageDataEvent . measurements [ key + " durationElapsed" ] = elapsedTime ; } this . usageDataClient . trackEvent ( usageDataEvent ) ; this . eventsSent ++ ; } catch ( err ) { this . reportError ( "sendUsageDataEvents" , err ) ; throw new Error ( err ) ; } } } param errorName param err public async reportError ( errorName , err ) { if ( this . getUsageDataLevel ( ) === UsageDataLevel . on ) { this . reportErrorApplicationInsights ( errorName , err ) ; } } param errorName param err public async reportErrorApplicationInsights ( errorName , err ) { if ( this . getUsageDataLevel ( ) === UsageDataLevel . on ) { err . name = this . options . isForTesting ? `${ errorName }-test` : errorName ; this . usageDataClient . trackException ( { exception : this . maskFilePaths ( err ) , } ) ; this . exceptionsSent ++ ; } } param testData param testReponse public usageDataOptIn ( testData = this . options . isForTesting , testResponse = "" ) { try { let response = "" ; if ( testData ) { response = testResponse ; } else { response = readLine . question ( `${ this . options . promptQuestion }\n` ) ; } if ( response . toLowerCase ( ) === "y" ) { this . options . usageDataLevel = UsageDataLevel . on ; } else { this . options . usageDataLevel = UsageDataLevel . off ; } jsonData . writeUsageDataJsonData ( this . options . groupName , this . options . usageDataLevel ) ; } catch ( err ) { this . reportError ( "UsageDataOptIn" , err ) ; throw new Error ( err ) ; } } /**
     * Stops usage data from being sent, by default usage data will be on
     */ public setUsageDataOff ( ) { appInsights . defaultClient . config . samplingPercentage = 0 ; } /**
     * Starts sending usage data, by default usage data will be on
     */ public setUsageDataOn ( ) { appInsights . defaultClient . config . samplingPercentage = 100 ; } returns public isUsageDataOn ( ) { return appInsights . defaultClient . config . samplingPercentage === 100 ; } returns public getUsageDataKey ( ) { return this . options . instrumentationKey ; } /**
     * Transform the project name by adddin '-test' suffix to it if necessary
     */ private getEventName ( ) { return this . options . isForTesting ? `${ this . options . projectName }-test` : this . options . projectName ; } returns public getEventsSent ( ) { return this . eventsSent ; } returns public getExceptionsSent ( ) { return this . exceptionsSent ; } returns public getUsageDataLevel ( ) { return this . options . usageDataLevel ; } returns public maskFilePaths ( err ) { try { const regexRemoveUserFilePaths = /[\/\\](.*)[\/\\]/gim ; const regexRemoveAbsoluteUserFilePathsFromStack = /\w:\\(?:[^\\\s]+\\)+/gim ; const regexRemoveFirstFilePathFromStack = /\\(.*)\./i ; err . message = err . message . replace ( regexRemoveUserFilePaths , "\\" ) ; err . stack = err . stack . replace ( regexRemoveFirstFilePathFromStack , "\\." ) ; err . stack = err . stack . replace ( regexRemoveAbsoluteUserFilePathsFromStack , "" ) ; return err ; } catch ( err ) { this . reportError ( "maskFilePaths" , err ) ; throw new Error ( err ) ; } } /**
     * Removes sensitive information fields from ApplicationInsights data
     */ private removeApplicationInsightsSensitiveInformation ( ) { delete this . usageDataClient . context . tags [ "ai.cloud.roleInstance" ] ; delete this . usageDataClient . context . tags [ "ai.device.id" ] ; delete this . usageDataClient . context . tags [ "ai.user.accountId" ] ; } param method param err param data public reportException ( method , err , data = { } ) { if ( this . getUsageDataLevel ( ) === UsageDataLevel . on ) { try { if ( err instanceof ExpectedError ) { this . reportExpectedException ( method , err , data ) ; return ; } let error = err instanceof Error ? Object . create ( err ) : new Error ( `${ this . options . projectName } error: ${ err }` ) ; error . name = this . getEventName ( ) ; let exceptionTelemetryObj = { exception : this . maskFilePaths ( error ) , properties : { } , } ; Object . entries ( { Succeeded : false , Method : method , ExpectedError : false , ... this . defaultData , ... data , } ) . forEach ( ( entry ) => { exceptionTelemetryObj . properties [ entry [ 0 ] ] = JSON . stringify ( entry [ 1 ] ) ; } ) ; this . usageDataClient . trackException ( exceptionTelemetryObj ) ; this . exceptionsSent ++ ; } catch ( e ) { this . reportError ( "reportException" , e ) ; throw e ; } } } param method param err param data public reportExpectedException ( method , err , data = { } ) { let error = err instanceof Error ? err : new Error ( `${ this . options . projectName } error: ${ err }` ) ; error . name = this . getEventName ( ) ; this . maskFilePaths ( error ) ; const errorMessage = error instanceof Error ? error . message : error ; this . sendUsageDataEvent ( { Succeeded : true , Method : method , ExpectedError : true , Error : errorMessage , ... data , } ) ; } param method param data public reportSuccess ( method , data = { } ) { this . sendUsageDataEvent ( { Succeeded : true , Method : method , ExpectedError : false , ... data , } ) ; } param method param err param data deprecated public sendUsageDataException ( method , err , data = { } ) { if ( this . getUsageDataLevel ( ) === UsageDataLevel . on ) { try { let error = err instanceof Error ? err : new Error ( `${ this . options . projectName } error: ${ err }` ) ; error . name = this . getEventName ( ) ; let exceptionTelemetryObj = { exception : this . maskFilePaths ( error ) , properties : { } , } ; Object . entries ( { Succeeded : false , Method : method , ... this . defaultData , ... data , } ) . forEach ( ( entry ) => { exceptionTelemetryObj . properties [ entry [ 0 ] ] = JSON . stringify ( entry [ 1 ] ) ; } ) ; this . usageDataClient . trackException ( exceptionTelemetryObj ) ; this . exceptionsSent ++ ; } catch ( e ) { this . reportError ( "sendUsageDataException" , e ) ; throw e ; } } } param method param data deprecated public sendUsageDataSuccessEvent ( method , data = { } ) { this . sendUsageDataEvent ( { Succeeded : true , Method : method , Pass : true , ... data , } ) ; } param method param data deprecated public sendUsageDataSuccessfulFailEvent ( method , data = { } ) { this . sendUsageDataEvent ( { Succeeded : true , Method : method , Pass : false , ... data , } ) ; } param data public sendUsageDataEvent ( data = { } ) { if ( this . getUsageDataLevel ( ) === UsageDataLevel . on ) { try { let eventTelemetryObj = new appInsights . Contracts . EventData ( ) ; eventTelemetryObj . name = this . getEventName ( ) ; eventTelemetryObj . properties = { ... this . defaultData , ... data , } ; this . usageDataClient . trackEvent ( eventTelemetryObj ) ; this . eventsSent ++ ; } catch ( e ) { this . reportError ( "sendUsageDataEvent" , e ) ; } } } }