import { Event , EventProcessor , Hub , Integration } from '@sentry/types' ; import { getGlobalObject , logger , normalize , uuid4 } from '@sentry/utils' ; import localForage from 'localforage' ; type LocalForage = { setItem < T > ( key , value , callback ? ) : Promise < T > ; iterate < T , U > ( iteratee , callback ? ) : Promise < U > ; removeItem ( key , callback ? ) : Promise < void > ; } ; /**
 * cache offline errors and send when connected
 */ export class Offline implements Integration { inheritDoc public static id : string = 'Offline' ; inheritDoc public readonly name : string = Offline . id ; /**
     * the global instance
     */ public global : any ; /**
     * the current hub instance
     */ public hub ? : Hub ; /**
     * maximum number of events to store while offline
     */ public maxStoredEvents : number ; /**
     * event cache
     */ public offlineEventStore : LocalForage ; inheritDoc public constructor ( options = { } ) { this . global = getGlobalObject < any > ( ) ; this . maxStoredEvents = options . maxStoredEvents || 30 ; this . offlineEventStore = localForage . createInstance ( { name : 'sentry/offlineEventStore' , } ) ; } inheritDoc public setupOnce ( addGlobalEventProcessor , getCurrentHub ) { this . hub = getCurrentHub ( ) ; if ( 'addEventListener' in this . global ) { this . global . addEventListener ( 'online' , ( ) => { void this . _sendEvents ( ) . catch ( ( ) => { logger . warn ( 'could not send cached events' ) ; } ) ; } ) ; } addGlobalEventProcessor ( ( event ) => { if ( this . hub && this . hub . getIntegration ( Offline ) ) { if ( 'navigator' in this . global && 'onLine' in this . global . navigator && ! this . global . navigator . onLine ) { void this . _cacheEvent ( event ) . then ( ( _event ) : Promise < void > => this . _enforceMaxEvents ( ) ) . catch ( ( _error ) : void => { logger . warn ( 'could not cache event while offline' ) ; } ) ; return null ; } } return event ; } ) ; if ( 'navigator' in this . global && 'onLine' in this . global . navigator && this . global . navigator . onLine ) { void this . _sendEvents ( ) . catch ( ( ) => { logger . warn ( 'could not send cached events' ) ; } ) ; } } param event private async _cacheEvent ( event ) { return this . offlineEventStore . setItem < Event > ( uuid4 ( ) , normalize ( event ) ) ; } /**
     * purge excess events if necessary
     */ private async _enforceMaxEvents ( ) { const events = [ ] ; return this . offlineEventStore . iterate < Event , void > ( ( event , cacheKey , _index ) : void => { events . push ( { cacheKey , event } ) ; } ) . then ( ( ) : Promise < void > => this . _purgeEvents ( events . sort ( ( a , b ) => ( b . event . timestamp || 0 ) - ( a . event . timestamp || 0 ) ) . slice ( this . maxStoredEvents < events . length ? this . maxStoredEvents : events . length ) . map ( event => event . cacheKey ) ) ) . catch ( ( _error ) : void => { logger . warn ( 'could not enforce max events' ) ; } ) ; } /**
     * purge event from cache
     */ private async _purgeEvent ( cacheKey ) { return this . offlineEventStore . removeItem ( cacheKey ) ; } /**
     * purge events from cache
     */ private async _purgeEvents ( cacheKeys ) { return Promise . all ( cacheKeys . map ( cacheKey => this . _purgeEvent ( cacheKey ) ) ) . then ( ) ; } /**
     * send all events
     */ private async _sendEvents ( ) { return this . offlineEventStore . iterate < Event , void > ( ( event , cacheKey , _index ) : void => { if ( this . hub ) { this . hub . captureEvent ( event ) ; void this . _purgeEvent ( cacheKey ) . catch ( ( _error ) : void => { logger . warn ( 'could not purge event from cache' ) ; } ) ; } else { logger . warn ( 'no hub found - could not send cached event' ) ; } } ) ; } }