import { EventProcessor , Hub , Integration , IntegrationClass , Scope , Span , Transaction } from '@sentry/types' ; import { basename , getGlobalObject , logger , timestampWithMs } from '@sentry/utils' ; deprecated sentry sentry const TRACING_GETTER = ( { id : 'Tracing' , } as any ) as IntegrationClass < Integration > ; /**
 * Used to extract BrowserTracing integration from @sentry/tracing
 */ const BROWSER_TRACING_GETTER = ( { id : 'BrowserTracing' , } as any ) as IntegrationClass < Integration > ; const VUE_OP = 'ui.vue' ; /** Global Vue object limited to the methods/attributes we require */ interface VueInstance { config : { errorHandler ? ( error , vm ? , info ? ) : void ; } ; util ? : { warn ( ... input ) : void ; } ; mixin ( hooks ) : void ; } /** Representation of Vue component internals */ interface ViewModel { [ key ] : any ; $root : object ; $options : { [ key ] : any ; name ? : string ; propsData ? : { [ key ] : any ; } ; _componentTag ? : string ; __file ? : string ; $_sentryPerfHook ? : boolean ; } ; $once ( hook , cb ) : void ; } /** Vue Integration configuration */ interface IntegrationOptions { /** Vue instance to be used inside the integration */ Vue : VueInstance ; /**
     * When set to `false`, Sentry will suppress reporting of all props data
     * from your Vue components for privacy concerns.
     */ attachProps : boolean ; /**
     * When set to `true`, original Vue's `logError` will be called as well.
     * https://github.com/vuejs/vue/blob/c2b1cfe9ccd08835f2d99f6ce60f67b4de55187f/src/core/util/error.js#L38-L48
     */ logErrors : boolean ; /**
     * When set to `true`, enables tracking of components lifecycle performance.
     * It requires `Tracing` integration to be also enabled.
     */ tracing : boolean ; /**  TracingOptions tracingOptions : TracingOptions ; } /** Vue specific configuration for Tracing Integration  */ interface TracingOptions { /**
     * Decides whether to track components by hooking into its lifecycle methods.
     * Can be either set to `boolean` to enable/disable tracking for all of them.
     * Or to an array of specific component names (case-sensitive).
     */ trackComponents : boolean | string [ ] ; /** How long to wait until the tracked root activity is marked as finished and sent of to Sentry */ timeout : number ; /**
     * List of hooks to keep track of during component lifecycle.
     * Available hooks: 'activate' | 'create' | 'destroy' | 'mount' | 'update'
     * Based on https://vuejs.org/v2/api/#Options-Lifecycle-Hooks
     */ hooks : Operation [ ] ; } /** Optional metadata attached to Sentry Event */ interface Metadata { [ key ] : any ; componentName ? : string ; propsData ? : { [ key ] : any ; } ; lifecycleHook ? : string ; } type Hook = 'activated' | 'beforeCreate' | 'beforeDestroy' | 'beforeMount' | 'beforeUpdate' | 'created' | 'deactivated' | 'destroyed' | 'mounted' | 'updated' ; type Operation = 'activate' | 'create' | 'destroy' | 'mount' | 'update' ; const HOOKS = { activate : [ 'activated' , 'deactivated' ] , create : [ 'beforeCreate' , 'created' ] , destroy : [ 'beforeDestroy' , 'destroyed' ] , mount : [ 'beforeMount' , 'mounted' ] , update : [ 'beforeUpdate' , 'updated' ] , } ; const COMPONENT_NAME_REGEXP = /(?:^|[-_/])(\w)/g ; const ROOT_COMPONENT_NAME = 'root' ; const ANONYMOUS_COMPONENT_NAME = 'anonymous component' ; /** JSDoc */ export class Vue implements Integration { inheritDoc public static id : string = 'Vue' ; inheritDoc public name : string = Vue . id ; private readonly _options : IntegrationOptions ; /**
     * Cache holding already processed component names
     */ private readonly _componentsCache : { [ key ] : string ; } = { } ; private _rootSpan ? : Span ; private _rootSpanTimer ? : ReturnType < typeof setTimeout > ; private _tracingActivity ? : number ; inheritDoc public constructor ( options ) { logger . log ( 'You are still using the Vue.js integration, consider moving to @sentry/vue' ) ; this . _options = { Vue : getGlobalObject < any > ( ) . Vue , attachProps : true , logErrors : false , tracing : false , ... options , tracingOptions : { hooks : [ 'mount' , 'update' ] , timeout : 2000 , trackComponents : false , ... options . tracingOptions , } , } ; } inheritDoc public setupOnce ( _ , getCurrentHub ) { if ( ! this . _options . Vue ) { logger . error ( 'Vue integration is missing a Vue instance' ) ; return ; } this . _attachErrorHandler ( getCurrentHub ) ; if ( this . _options . tracing ) { this . _startTracing ( getCurrentHub ) ; } } /**
     * Extract component name from the ViewModel
     */ private _getComponentName ( vm ) { if ( ! vm ) { return ANONYMOUS_COMPONENT_NAME ; } if ( vm . $root === vm ) { return ROOT_COMPONENT_NAME ; } if ( ! vm . $options ) { return ANONYMOUS_COMPONENT_NAME ; } if ( vm . $options . name ) { return vm . $options . name ; } if ( vm . $options . _componentTag ) { return vm . $options . _componentTag ; } if ( vm . $options . __file ) { const unifiedFile = vm . $options . __file . replace ( /^[a-zA-Z]:/ , '' ) . replace ( /\\/g , '/' ) ; const filename = basename ( unifiedFile , '.vue' ) ; return ( this . _componentsCache [ filename ] || ( this . _componentsCache [ filename ] = filename . replace ( COMPONENT_NAME_REGEXP , ( _ , c ) => c ? c . toUpperCase ( ) : '' ) ) ) ; } return ANONYMOUS_COMPONENT_NAME ; } /** Keep it as attribute function, to keep correct `this` binding inside the hooks callbacks  */ private readonly _applyTracingHooks = ( vm , getCurrentHub ) : void => { if ( vm . $options . $_sentryPerfHook ) { return ; } vm . $options . $_sentryPerfHook = true ; const name = this . _getComponentName ( vm ) ; const rootMount = name === ROOT_COMPONENT_NAME ; const spans = { } ; const rootHandler = ( hook ) : void => { const now = timestampWithMs ( ) ; if ( this . _rootSpan ) { this . _finishRootSpan ( now , getCurrentHub ) ; } else { vm . $once ( `hook:${ hook }` , ( ) => { const tracingIntegration = getCurrentHub ( ) . getIntegration ( TRACING_GETTER ) ; if ( tracingIntegration ) { this . _tracingActivity = ( tracingIntegration as any ) . constructor . pushActivity ( 'Vue Application Render' ) ; const transaction = ( tracingIntegration as any ) . constructor . getTransaction ( ) ; if ( transaction ) { this . _rootSpan = transaction . startChild ( { description : 'Application Render' , op : VUE_OP , } ) ; } } else { const activeTransaction = getActiveTransaction ( getCurrentHub ( ) ) ; if ( activeTransaction ) { this . _rootSpan = activeTransaction . startChild ( { description : 'Application Render' , op : VUE_OP , } ) ; } } } ) ; } } ; const childHandler = ( hook , operation ) : void => { const shouldTrack = Array . isArray ( this . _options . tracingOptions . trackComponents ) ? this . _options . tracingOptions . trackComponents . indexOf ( name ) > - 1 : this . _options . tracingOptions . trackComponents ; if ( ! this . _rootSpan || ! shouldTrack ) { return ; } const now = timestampWithMs ( ) ; const span = spans [ operation ] ; if ( span ) { span . finish ( ) ; this . _finishRootSpan ( now , getCurrentHub ) ; } else { vm . $once ( `hook:${ hook }` , ( ) => { if ( this . _rootSpan ) { spans [ operation ] = this . _rootSpan . startChild ( { description : `Vue <${ name }>` , op : `${ VUE_OP }.${ operation }` , } ) ; } } ) ; } } ; this . _options . tracingOptions . hooks . forEach ( operation => { const internalHooks = HOOKS [ operation ] ; if ( ! internalHooks ) { logger . warn ( `Unknown hook: ${ operation }` ) ; return ; } internalHooks . forEach ( internalHook => { const handler = rootMount ? rootHandler . bind ( this , internalHook ) : childHandler . bind ( this , internalHook , operation ) ; const currentValue = vm . $options [ internalHook ] ; if ( Array . isArray ( currentValue ) ) { vm . $options [ internalHook ] = [ handler , ... currentValue ] ; } else if ( typeof currentValue === 'function' ) { vm . $options [ internalHook ] = [ handler , currentValue ] ; } else { vm . $options [ internalHook ] = [ handler ] ; } } ) ; } ) ; } ; /** Finish top-level span and activity with a debounce configured using `timeout` option */ private _finishRootSpan ( timestamp , getCurrentHub ) { if ( this . _rootSpanTimer ) { clearTimeout ( this . _rootSpanTimer ) ; } this . _rootSpanTimer = setTimeout ( ( ) => { if ( this . _tracingActivity ) { const tracingIntegration = getCurrentHub ( ) . getIntegration ( TRACING_GETTER ) ; if ( tracingIntegration ) { ( tracingIntegration as any ) . constructor . popActivity ( this . _tracingActivity ) ; } } if ( this . _rootSpan ) { this . _rootSpan . finish ( timestamp ) ; } } , this . _options . tracingOptions . timeout ) ; } /** Inject configured tracing hooks into Vue's component lifecycles */ private _startTracing ( getCurrentHub ) { const applyTracingHooks = this . _applyTracingHooks ; this . _options . Vue . mixin ( { beforeCreate ( this ) { if ( getCurrentHub ( ) . getIntegration ( TRACING_GETTER ) || getCurrentHub ( ) . getIntegration ( BROWSER_TRACING_GETTER ) ) { applyTracingHooks ( this , getCurrentHub ) ; } else { logger . error ( 'Vue integration has tracing enabled, but Tracing integration is not configured' ) ; } } , } ) ; } /** Inject Sentry's handler into owns Vue's error handler  */ private _attachErrorHandler ( getCurrentHub ) { const currentErrorHandler = this . _options . Vue . config . errorHandler ; this . _options . Vue . config . errorHandler = ( error , vm ? , info ? ) : void => { const metadata = { } ; if ( vm ) { try { metadata . componentName = this . _getComponentName ( vm ) ; if ( this . _options . attachProps ) { metadata . propsData = vm . $options . propsData ; } } catch ( _oO ) { logger . warn ( 'Unable to extract metadata from Vue component.' ) ; } } if ( info ) { metadata . lifecycleHook = info ; } if ( getCurrentHub ( ) . getIntegration ( Vue ) ) { setTimeout ( ( ) => { getCurrentHub ( ) . withScope ( scope => { scope . setContext ( 'vue' , metadata ) ; getCurrentHub ( ) . captureException ( error ) ; } ) ; } ) ; } if ( typeof currentErrorHandler === 'function' ) { currentErrorHandler . call ( this . _options . Vue , error , vm , info ) ; } if ( this . _options . logErrors ) { if ( this . _options . Vue . util ) { this . _options . Vue . util . warn ( `Error in ${ info }: "${ error && error . toString ( ) }"` , vm ) ; } console . error ( error ) ; } } ; } } interface HubType extends Hub { getScope ? ( ) : Scope | undefined ; } /** Grabs active transaction off scope */ export function getActiveTransaction < T extends Transaction > ( hub ) { if ( hub && hub . getScope ) { const scope = hub . getScope ( ) as Scope ; if ( scope ) { return scope . getTransaction ( ) as T | undefined ; } } return undefined ; }