import { Scope } from '../hub' ; import { Client , Event , EventHint , EventTypeEnum , Integration , IntegrationClass , Options } from '../types' ; import { isDevHref , isThenable , logger , normalize , SyncPromise , truncate } from '../utils' ; import { Backend , BackendClass } from './basebackend' ; import { IntegrationIndex , setupIntegrations } from './integration' ; const DEFAULT_MAX_COUNT = 25 ; 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 ; /** Array of used integrations. */ protected _integrations : IntegrationIndex = { } ; /** Is the client still processing a call? */ protected _processing : boolean = false ; private _count : number = DEFAULT_MAX_COUNT ; param backendClass param options protected constructor ( backendClass , options ) { this . _backend = new backendClass ( options ) ; this . _options = options ; } inheritDoc public captureException ( exception , hint ? , scope ? ) { let eventId = hint && hint . event_id ; this . _processing = true ; this . _getBackend ( ) . eventFromException ( exception , hint ) . then ( event => { event . type = EventTypeEnum . exception ; eventId = this . captureEvent ( event , hint , scope ) ; } ) ; return eventId ; } private _devEnvHandle ( ) { if ( isDevHref ( ) ) { logger . warn ( `监控你在本地服务进行开发，不会进行北斗上报` ) ; return true ; } return true ; } private _limitCount ( event ) { if ( event . type === 'breadcrumb' || event . type === 'performance' ) { return true ; } if ( this . _count <= 0 ) { logger . warn ( `当前发送次数已经用完` ) ; return false ; } logger . log ( `当前剩余发送event次数: ${ this . _count }` ) ; this . _count = this . _count - 1 ; return true ; } inheritDoc public captureEvent ( event , hint ? , scope ? ) { let eventId = hint && hint . event_id ; this . _processing = true ; this . _processEvent ( event , hint , scope ) . then ( finalEvent => { eventId = finalEvent && finalEvent . event_id ; this . _processing = false ; } ) . then ( null , reason => { this . _processing = false ; } ) ; return eventId ; } inheritDoc public getOptions ( ) { return this . _options ; } /**
     * Sets up the integrations
     */ public setupIntegrations ( ) { if ( this . _isEnabled ( ) ) { 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 ; } } /** Returns the current backend. */ protected _getBackend ( ) { return this . _backend ; } /** Determines whether this SDK is enabled. */ protected _isEnabled ( ) { return this . getOptions ( ) . enabled !== false ; } /** isProd */ protected _isProd ( ) { return ! ! this . getOptions ( ) . isProd ; } /** isDebug */ protected _isDebug ( ) { return ! ! this . getOptions ( ) . isDebug ; } param event param hint param scope returns public _prepareEvent ( event , scope ? , hint ? ) { const { normalizeDepth = 3 } = this . getOptions ( ) ; const prepared = { ... event , } ; this . _applyClientOptions ( prepared ) ; let finalScope = scope ; if ( hint && hint . captureContext ) { finalScope = Scope . clone ( finalScope ) . update ( hint . captureContext ) ; } let result = SyncPromise . resolve < 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 . contexts && { contexts : normalize ( event . contexts , depth ) , } ) , } ; if ( event . contexts && event . contexts . trace ) { normalized . contexts . trace = event . contexts . trace ; } return normalized ; } param event protected _applyClientOptions ( event ) { const { maxValueLength = 250 } = this . getOptions ( ) ; const exception = event . exceptions && event . exceptions [ 0 ] ; if ( exception && exception . content ) { exception . content = truncate ( exception . content , maxValueLength ) ; } const request = event . request ; if ( request && request . url ) { request . url = truncate ( request . url , maxValueLength ) ; } } param event public _sendEvent ( event ) { if ( this . _isProd ( ) && this . _devEnvHandle ( ) && this . _limitCount ( event ) ) { this . _getBackend ( ) . sendEvent ( event ) ; } if ( this . _isDebug ( ) ) { logger . log ( '1' , JSON . stringify ( event ) ) ; } } param event param hint param scope returns protected _processEvent ( event , hint ? , scope ? ) { const { beforeSend } = this . getOptions ( ) ; if ( ! this . _isEnabled ( ) ) { return SyncPromise . reject ( 'SDK not enabled, will not send event.' ) ; } return new SyncPromise ( ( resolve , reject ) => { this . _prepareEvent ( event , scope , hint ) . then ( prepared => { if ( prepared === null ) { reject ( 'An event processor returned null, will not send event.' ) ; return ; } let finalEvent = prepared ; const isInternalException = hint && hint . data && ( hint . data as { [ key ] : unknown ; } ) . __beidou__ === true ; if ( isInternalException || ! beforeSend ) { this . _sendEvent ( finalEvent ) ; resolve ( finalEvent ) ; return ; } const beforeSendResult = beforeSend ( prepared , hint ) ; if ( typeof beforeSendResult === 'undefined' ) { logger . error ( '`beforeSend` method has to return `null` or a valid event.' ) ; } else if ( isThenable ( beforeSendResult ) ) { this . _handleAsyncBeforeSend ( beforeSendResult as PromiseLike < Event | null > , resolve , reject ) ; } else { finalEvent = beforeSendResult as Event | null ; if ( finalEvent === null ) { logger . log ( '`beforeSend` returned `null`, will not send event.' ) ; resolve ( null ) ; return ; } this . _sendEvent ( finalEvent ) ; resolve ( finalEvent ) ; } } ) . then ( null , reason => { this . captureException ( reason , { data : { __beidou__ : true , } , originalException : reason as Error , } ) ; reject ( `Event processing pipeline threw an error, original event will not be sent. Details have been sent as a new event.\nReason: ${ reason }` ) ; } ) ; } ) ; } /**
     * Resolves before send Promise and calls resolve/reject on parent SyncPromise.
     */ private _handleAsyncBeforeSend ( beforeSend , resolve , reject ) { beforeSend . then ( processedEvent => { if ( processedEvent === null ) { reject ( '`beforeSend` returned `null`, will not send event.' ) ; return ; } this . _sendEvent ( processedEvent ) ; resolve ( processedEvent ) ; } ) . then ( null , e => { reject ( `beforeSend rejected with ${ e }` ) ; } ) ; } }