import type { TimingResource } from './browser-timings.h' ; import { Timings } from './browser-timings.h' ; const toSecond = ( timeStamp ) => Math . round ( timeStamp ) / 1000 ; const testString = ( pattern ) => ( text ) => pattern . test ( text ) ; const isJs = testString ( /\.js$/ ) ; const isCSS = testString ( /\.css$/ ) ; const isImg = testString ( /\.(?:svg|png|jpeg|webp|jpg)$/ ) ; const isFont = testString ( /\.(?:woff|eot|woff2|ttf)/ ) ; enum resourceType { html = 'html' , js = 'js' , css = 'css' , img = 'img' , font = 'font' , other = 'other' } const getType = ( name ) => { switch ( true ) { case isCSS ( name ) : return resourceType . css ; case isJs ( name ) : return resourceType . js ; case isImg ( name ) : return resourceType . img ; case isFont ( name ) : return resourceType . font ; default : return resourceType . other ; } } ; const getEntriesByType = ( performance , type ) => { if ( performance . getEntriesByType ) { return performance . getEntriesByType ( type ) ; } } ; const getPaintMetric = ( performance ) => { const paint = getEntriesByType ( performance , 'paint' ) ; if ( paint && paint [ 0 ] ) { return paint [ 0 ] . startTime ; } return 0 ; } ; const getTimingResourceValue = ( tr , key ) : number => { return ( tr && tr [ key ] ) || 0 ; } ; const calculateMetric = ( prev , next ) : TimingResource => { return { duration : getTimingResourceValue ( prev , 'duration' ) + toSecond ( next . duration ) , transferSize : getTimingResourceValue ( prev , 'transferSize' ) + next . transferSize , encodedBodySize : getTimingResourceValue ( prev , 'encodedBodySize' ) + next . encodedBodySize , } ; } ; const getResourceMetrics = ( performance ) => { const result = { } as Record < string , any > ; const entries = getEntriesByType ( performance , 'resource' ) as PerformanceResourceTiming [ ] ; if ( entries && entries . length > 0 ) { entries . forEach ( ( entry ) => { const name = getType ( entry . name . split ( '?' , 1 ) [ 0 ] ) ; if ( resourceType [ name ] ) { result [ name ] = calculateMetric ( result [ name ] , entry ) ; } } ) ; } const pageNav = getEntriesByType ( performance , 'navigation' ) as PerformanceNavigationTiming [ ] ; if ( pageNav && pageNav . length > 0 ) { result [ resourceType . html ] = calculateMetric ( { } , pageNav [ 0 ] ) ; } return result ; } ; type TimingsList = Array < { key : string ; start : number ; end : number ; } > ; export function browserTimings ( ) { let performance ; try { performance = window . performance ; if ( ! performance . timing || ! performance . timing . loadEventEnd ) { if ( performance . timing . loadEventEnd === 0 ) { console . error ( 'Library used at browser initialization page. Reed docs' ) ; } throw new Error ( 'Not implemented timing api' ) ; } } catch ( e ) { return { } ; } const { requestStart , responseStart , responseEnd , domComplete , loadEventEnd , domInteractive , navigationStart , } = performance . timing ; const timingCandidates = [ { key : 'connection' , start : navigationStart , end : requestStart , } , { key : 'backend' , start : requestStart , end : responseStart , } , { key : 'pageDownload' , start : responseStart , end : responseEnd , } , { key : 'first-paint' , start : 0 , end : getPaintMetric ( performance ) , } , { key : 'domInteractive' , start : navigationStart , end : domInteractive , } , { key : 'domComplete' , start : navigationStart , end : domComplete , } , { key : 'pageLoadTime' , start : navigationStart , end : loadEventEnd , } , ] ; const timings = { } ; timingCandidates . forEach ( ( el ) => { const time = toSecond ( el . end - el . start ) ; timings [ el . key ] = time > 0 || time < 2000 ? time : undefined ; } ) ; timings . download = getResourceMetrics ( performance ) ; return timings as Timings ; } export { Timings } ;