import { Scope , Session } from '@sentry/hub' ; import { Client , DsnComponents , Event , EventHint , Integration , IntegrationClass , Options , SeverityLevel , Transport , } from '@sentry/types' ; import { checkOrSetAlreadyCaught , dateTimestampInSeconds , isDebugBuild , isPlainObject , isPrimitive , isThenable , logger , makeDsn , normalize , rejectedSyncPromise , resolvedSyncPromise , SentryError , SyncPromise , truncate , uuid4 , } from '@sentry/utils' ; import { Backend , BackendClass } from './basebackend' ; import { IntegrationIndex , setupIntegrations } from './integration' ; const ALREADY_SEEN_ERROR = "Not capturing exception because it's already been captured." ; /**
 * Base implementation for all JavaScript SDK clients.
 *
 * Call the constructor with the corresponding backend constructor and options
 * specific to the client subclass. To access these options later, use
 *  Client . getOptions . Also, the Backend instance is available via
 *  Client . getBackend .
 *
 * If a Dsn is specified in the options, it will be parsed and stored. Use
 *  Client . getDsn to retrieve the Dsn at any moment. In case the Dsn is
 * invalid, the constructor will throw a  SentryException . Note that
 * without a valid Dsn, the SDK will not send any events to Sentry.
 *
 * Before sending an event via the backend, it is passed through
 *  BaseClient . _prepareEvent to add SDK information and scope data
 * (breadcrumbs and context). To add more custom information, override this
 * method and extend the resulting prepared event.
 *
 * To issue automatically created events (e.g. via instrumentation), use
 *  Client . captureEvent . It will prepare the event and pass it through
 * the callback lifecycle. To issue auto-breadcrumbs, use
 *  Client . addBreadcrumb .
 *
 *  example export abstract class BaseClient < B extends Backend , O extends Options > implements Client < O > { /**
     * The backend used to physically interact in the environment. Usually, this
     * will correspond to the client. When composing SDKs, however, the Backend
     * from the root SDK will be used.
     */ protected readonly _backend : B ; /** Options passed to the SDK. */ protected readonly _options : O ; /** The client Dsn, if specified in options. Without this Dsn, the SDK will be disabled. */ protected readonly _dsn ? : DsnComponents ; /** Array of used integrations. */ protected _integrations : IntegrationIndex = { } ; /** Number of calls being processed */ protected _numProcessing : number = 0 ; param backendClass param options protected constructor ( backendClass , options ) { this . _backend = new backendClass ( options ) ; this . _options = options ; if ( options . dsn ) { this . _dsn = makeDsn ( options . dsn ) ; } } inheritDoc public captureException ( exception , hint ? , scope ? ) { if ( checkOrSetAlreadyCaught ( exception ) ) { logger . log ( ALREADY_SEEN_ERROR ) ; return ; } let eventId = hint && hint . event_id ; this . _process ( this . _getBackend ( ) . eventFromException ( exception , hint ) . then ( event => this . _captureEvent ( event , hint , scope ) ) . then ( result => { eventId = result ; } ) ) ; return eventId ; } inheritDoc public captureMessage ( message , level ? , hint ? , scope ? ) { let eventId = hint && hint . event_id ; const promisedEvent = isPrimitive ( message ) ? this . _getBackend ( ) . eventFromMessage ( String ( message ) , level , hint ) : this . _getBackend ( ) . eventFromException ( message , hint ) ; this . _process ( promisedEvent . then ( event => this . _captureEvent ( event , hint , scope ) ) . then ( result => { eventId = result ; } ) ) ; return eventId ; } inheritDoc public captureEvent ( event , hint ? , scope ? ) { if ( hint && hint . originalException && checkOrSetAlreadyCaught ( hint . originalException ) ) { logger . log ( ALREADY_SEEN_ERROR ) ; return ; } let eventId = hint && hint . event_id ; this . _process ( this . _captureEvent ( event , hint , scope ) . then ( result => { eventId = result ; } ) ) ; return eventId ; } inheritDoc public captureSession ( session ) { if ( ! this . _isEnabled ( ) ) { if ( isDebugBuild ( ) ) { logger . warn ( 'SDK not enabled, will not capture session.' ) ; } return ; } if ( ! ( typeof session . release === 'string' ) ) { if ( isDebugBuild ( ) ) { logger . warn ( 'Discarded session because of missing or non-string release' ) ; } } else { this . _sendSession ( session ) ; session . update ( { init : false } ) ; } } inheritDoc public getDsn ( ) { return this . _dsn ; } inheritDoc public getOptions ( ) { return this . _options ; } inheritDoc public getTransport ( ) { return this . _getBackend ( ) . getTransport ( ) ; } inheritDoc public flush ( timeout ? ) { return this . _isClientDoneProcessing ( timeout ) . then ( clientFinished => { return this . getTransport ( ) . close ( timeout ) . then ( transportFlushed => clientFinished && transportFlushed ) ; } ) ; } inheritDoc public close ( timeout ? ) { return this . flush ( timeout ) . then ( result => { this . getOptions ( ) . enabled = false ; return result ; } ) ; } /**
     * Sets up the integrations
     */ public setupIntegrations ( ) { if ( this . _isEnabled ( ) && ! this . _integrations . initialized ) { this . _integrations = setupIntegrations ( this . _options ) ; } } inheritDoc public getIntegration < T extends Integration > ( integration ) { try { return ( this . _integrations [ integration . id ] as T ) || null ; } catch ( _oO ) { logger . warn ( `Cannot retrieve integration ${ integration . id } from the current Client` ) ; return null ; } } /** Updates existing session based on the provided event */ protected _updateSessionFromEvent ( session , event ) { let crashed = false ; let errored = false ; const exceptions = event . exception && event . exception . values ; if ( exceptions ) { errored = true ; for ( const ex of exceptions ) { const mechanism = ex . mechanism ; if ( mechanism && mechanism . handled === false ) { crashed = true ; break ; } } } const sessionNonTerminal = session . status === 'ok' ; const shouldUpdateAndSend = ( sessionNonTerminal && session . errors === 0 ) || ( sessionNonTerminal && crashed ) ; if ( shouldUpdateAndSend ) { session . update ( { ... ( crashed && { status : 'crashed' } ) , errors : session . errors || Number ( errored || crashed ) , } ) ; this . captureSession ( session ) ; } } /** Deliver captured session to Sentry */ protected _sendSession ( session ) { this . _getBackend ( ) . sendSession ( session ) ; } param timeout returns protected _isClientDoneProcessing ( timeout ? ) { return new SyncPromise ( resolve => { let ticked = 0 ; const tick = 1 ; const interval = setInterval ( ( ) => { if ( this . _numProcessing == 0 ) { clearInterval ( interval ) ; resolve ( true ) ; } else { ticked += tick ; if ( timeout && ticked >= timeout ) { clearInterval ( interval ) ; resolve ( false ) ; } } } , tick ) ; } ) ; } /** Returns the current backend. */ protected _getBackend ( ) { return this . _backend ; } /** Determines whether this SDK is enabled and a valid Dsn is present. */ protected _isEnabled ( ) { return this . getOptions ( ) . enabled !== false && this . _dsn !== undefined ; } param event param hint param scope returns protected _prepareEvent ( event , scope ? , hint ? ) { const { normalizeDepth = 3 } = this . getOptions ( ) ; const prepared = { ... event , event_id : event . event_id || ( hint && hint . event_id ? hint . event_id : uuid4 ( ) ) , timestamp : event . timestamp || dateTimestampInSeconds ( ) , } ; this . _applyClientOptions ( prepared ) ; this . _applyIntegrationsMetadata ( prepared ) ; let finalScope = scope ; if ( hint && hint . captureContext ) { finalScope = Scope . clone ( finalScope ) . update ( hint . captureContext ) ; } let result = resolvedSyncPromise < Event | null > ( prepared ) ; if ( finalScope ) { result = finalScope . applyToEvent ( prepared , hint ) ; } return result . then ( evt => { if ( typeof normalizeDepth === 'number' && normalizeDepth > 0 ) { return this . _normalizeEvent ( evt , normalizeDepth ) ; } return evt ; } ) ; } param event returns protected _normalizeEvent ( event , depth ) { if ( ! event ) { return null ; } const normalized = { ... event , ... ( event . breadcrumbs && { breadcrumbs : event . breadcrumbs . map ( b => ( { ... b , ... ( b . data && { data : normalize ( b . data , depth ) , } ) , } ) ) , } ) , ... ( event . user && { user : normalize ( event . user , depth ) , } ) , ... ( event . contexts && { contexts : normalize ( event . contexts , depth ) , } ) , ... ( event . extra && { extra : normalize ( event . extra , depth ) , } ) , } ; if ( event . contexts && event . contexts . trace ) { normalized . contexts . trace = event . contexts . trace ; } const { _experiments = { } } = this . getOptions ( ) ; if ( _experiments . ensureNoCircularStructures ) { return normalize ( normalized ) ; } return normalized ; } param event protected _applyClientOptions ( event ) { const options = this . getOptions ( ) ; const { environment , release , dist , maxValueLength = 250 } = options ; if ( ! ( 'environment' in event ) ) { event . environment = 'environment' in options ? environment : 'production' ; } if ( event . release === undefined && release !== undefined ) { event . release = release ; } if ( event . dist === undefined && dist !== undefined ) { event . dist = dist ; } if ( event . message ) { event . message = truncate ( event . message , maxValueLength ) ; } const exception = event . exception && event . exception . values && event . exception . values [ 0 ] ; if ( exception && exception . value ) { exception . value = truncate ( exception . value , maxValueLength ) ; } const request = event . request ; if ( request && request . url ) { request . url = truncate ( request . url , maxValueLength ) ; } } param event protected _applyIntegrationsMetadata ( event ) { const integrationsArray = Object . keys ( this . _integrations ) ; if ( integrationsArray . length > 0 ) { event . sdk = event . sdk || { } ; event . sdk . integrations = [ ... ( event . sdk . integrations || [ ] ) , ... integrationsArray ] ; } } param event protected _sendEvent ( event ) { this . _getBackend ( ) . sendEvent ( event ) ; } param event param hint param scope protected _captureEvent ( event , hint ? , scope ? ) { return this . _processEvent ( event , hint , scope ) . then ( finalEvent => { return finalEvent . event_id ; } , reason => { logger . error ( reason ) ; return undefined ; } ) ; } param event param hint param scope returns protected _processEvent ( event , hint ? , scope ? ) { const { beforeSend , sampleRate } = this . getOptions ( ) ; const transport = this . getTransport ( ) ; type RecordLostEvent = NonNullable < Transport [ 'recordLostEvent' ] > ; type RecordLostEventParams = Parameters < RecordLostEvent > ; function recordLostEvent ( outcome , category ) { if ( transport . recordLostEvent ) { transport . recordLostEvent ( outcome , category ) ; } } if ( ! this . _isEnabled ( ) ) { return rejectedSyncPromise ( new SentryError ( 'SDK not enabled, will not capture event.' ) ) ; } const isTransaction = event . type === 'transaction' ; if ( ! isTransaction && typeof sampleRate === 'number' && Math . random ( ) > sampleRate ) { recordLostEvent ( 'sample_rate' , 'event' ) ; return rejectedSyncPromise ( new SentryError ( `Discarding event because it's not included in the random sample (sampling rate = ${ sampleRate })` ) ) ; } return this . _prepareEvent ( event , scope , hint ) . then ( prepared => { if ( prepared === null ) { recordLostEvent ( 'event_processor' , event . type || 'event' ) ; throw new SentryError ( 'An event processor returned null, will not send event.' ) ; } const isInternalException = hint && hint . data && ( hint . data as { __sentry__ : boolean ; } ) . __sentry__ === true ; if ( isInternalException || isTransaction || ! beforeSend ) { return prepared ; } const beforeSendResult = beforeSend ( prepared , hint ) ; return _ensureBeforeSendRv ( beforeSendResult ) ; } ) . then ( processedEvent => { if ( processedEvent === null ) { recordLostEvent ( 'before_send' , event . type || 'event' ) ; throw new SentryError ( '`beforeSend` returned `null`, will not send event.' ) ; } const session = scope && scope . getSession && scope . getSession ( ) ; if ( ! isTransaction && session ) { this . _updateSessionFromEvent ( session , processedEvent ) ; } this . _sendEvent ( processedEvent ) ; return processedEvent ; } ) . then ( null , reason => { if ( reason instanceof SentryError ) { throw reason ; } this . captureException ( reason , { data : { __sentry__ : true , } , originalException : reason as Error , } ) ; throw new SentryError ( `Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event.\nReason: ${ reason }` ) ; } ) ; } /**
     * Occupies the client with processing and event
     */ protected _process < T > ( promise ) { this . _numProcessing += 1 ; void promise . then ( value => { this . _numProcessing -= 1 ; return value ; } , reason => { this . _numProcessing -= 1 ; return reason ; } ) ; } } /**
 * Verifies that return value of configured `beforeSend` is of expected type.
 */ function _ensureBeforeSendRv ( rv ) { const nullErr = '`beforeSend` method has to return `null` or a valid event.' ; if ( isThenable ( rv ) ) { return rv . then ( event => { if ( ! ( isPlainObject ( event ) || event === null ) ) { throw new SentryError ( nullErr ) ; } return event ; } , e => { throw new SentryError ( `beforeSend rejected with ${ e }` ) ; } ) ; } else if ( ! ( isPlainObject ( rv ) || rv === null ) ) { throw new SentryError ( nullErr ) ; } return rv ; }