time.js
1 import { isNodeEnv, dynamicRequire } from './node.js'; 2 import { getGlobalObject } from './worldwide.js'; 3 4 // eslint-disable-next-line deprecation/deprecation 5 const WINDOW = getGlobalObject(); 6 7 /** 8 * An object that can return the current timestamp in seconds since the UNIX epoch. 9 */ 10 11 /** 12 * A TimestampSource implementation for environments that do not support the Performance Web API natively. 13 * 14 * Note that this TimestampSource does not use a monotonic clock. A call to `nowSeconds` may return a timestamp earlier 15 * than a previously returned value. We do not try to emulate a monotonic behavior in order to facilitate debugging. It 16 * is more obvious to explain "why does my span have negative duration" than "why my spans have zero duration". 17 */ 18 const dateTimestampSource = { 19 nowSeconds: () => Date.now() / 1000, 20 }; 21 22 /** 23 * A partial definition of the [Performance Web API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Performance} 24 * for accessing a high-resolution monotonic clock. 25 */ 26 27 /** 28 * Returns a wrapper around the native Performance API browser implementation, or undefined for browsers that do not 29 * support the API. 30 * 31 * Wrapping the native API works around differences in behavior from different browsers. 32 */ 33 function getBrowserPerformance() { 34 const { performance } = WINDOW; 35 if (!performance || !performance.now) { 36 return undefined; 37 } 38 39 // Replace performance.timeOrigin with our own timeOrigin based on Date.now(). 40 // 41 // This is a partial workaround for browsers reporting performance.timeOrigin such that performance.timeOrigin + 42 // performance.now() gives a date arbitrarily in the past. 43 // 44 // Additionally, computing timeOrigin in this way fills the gap for browsers where performance.timeOrigin is 45 // undefined. 46 // 47 // The assumption that performance.timeOrigin + performance.now() ~= Date.now() is flawed, but we depend on it to 48 // interact with data coming out of performance entries. 49 // 50 // Note that despite recommendations against it in the spec, browsers implement the Performance API with a clock that 51 // might stop when the computer is asleep (and perhaps under other circumstances). Such behavior causes 52 // performance.timeOrigin + performance.now() to have an arbitrary skew over Date.now(). In laptop computers, we have 53 // observed skews that can be as long as days, weeks or months. 54 // 55 // See https://github.com/getsentry/sentry-javascript/issues/2590. 56 // 57 // BUG: despite our best intentions, this workaround has its limitations. It mostly addresses timings of pageload 58 // transactions, but ignores the skew built up over time that can aversely affect timestamps of navigation 59 // transactions of long-lived web pages. 60 const timeOrigin = Date.now() - performance.now(); 61 62 return { 63 now: () => performance.now(), 64 timeOrigin, 65 }; 66 } 67 68 /** 69 * Returns the native Performance API implementation from Node.js. Returns undefined in old Node.js versions that don't 70 * implement the API. 71 */ 72 function getNodePerformance() { 73 try { 74 const perfHooks = dynamicRequire(module, 'perf_hooks') ; 75 return perfHooks.performance; 76 } catch (_) { 77 return undefined; 78 } 79 } 80 81 /** 82 * The Performance API implementation for the current platform, if available. 83 */ 84 const platformPerformance = isNodeEnv() ? getNodePerformance() : getBrowserPerformance(); 85 86 const timestampSource = 87 platformPerformance === undefined 88 ? dateTimestampSource 89 : { 90 nowSeconds: () => (platformPerformance.timeOrigin + platformPerformance.now()) / 1000, 91 }; 92 93 /** 94 * Returns a timestamp in seconds since the UNIX epoch using the Date API. 95 */ 96 const dateTimestampInSeconds = dateTimestampSource.nowSeconds.bind(dateTimestampSource); 97 98 /** 99 * Returns a timestamp in seconds since the UNIX epoch using either the Performance or Date APIs, depending on the 100 * availability of the Performance API. 101 * 102 * See `usingPerformanceAPI` to test whether the Performance API is used. 103 * 104 * BUG: Note that because of how browsers implement the Performance API, the clock might stop when the computer is 105 * asleep. This creates a skew between `dateTimestampInSeconds` and `timestampInSeconds`. The 106 * skew can grow to arbitrary amounts like days, weeks or months. 107 * See https://github.com/getsentry/sentry-javascript/issues/2590. 108 */ 109 const timestampInSeconds = timestampSource.nowSeconds.bind(timestampSource); 110 111 /** 112 * Re-exported with an old name for backwards-compatibility. 113 * TODO (v8): Remove this 114 * 115 * @deprecated Use `timestampInSeconds` instead. 116 */ 117 const timestampWithMs = timestampInSeconds; 118 119 /** 120 * A boolean that is true when timestampInSeconds uses the Performance API to produce monotonic timestamps. 121 */ 122 const usingPerformanceAPI = platformPerformance !== undefined; 123 124 /** 125 * Internal helper to store what is the source of browserPerformanceTimeOrigin below. For debugging only. 126 */ 127 let _browserPerformanceTimeOriginMode; 128 129 /** 130 * The number of milliseconds since the UNIX epoch. This value is only usable in a browser, and only when the 131 * performance API is available. 132 */ 133 const browserPerformanceTimeOrigin = (() => { 134 // Unfortunately browsers may report an inaccurate time origin data, through either performance.timeOrigin or 135 // performance.timing.navigationStart, which results in poor results in performance data. We only treat time origin 136 // data as reliable if they are within a reasonable threshold of the current time. 137 138 const { performance } = WINDOW; 139 if (!performance || !performance.now) { 140 _browserPerformanceTimeOriginMode = 'none'; 141 return undefined; 142 } 143 144 const threshold = 3600 * 1000; 145 const performanceNow = performance.now(); 146 const dateNow = Date.now(); 147 148 // if timeOrigin isn't available set delta to threshold so it isn't used 149 const timeOriginDelta = performance.timeOrigin 150 ? Math.abs(performance.timeOrigin + performanceNow - dateNow) 151 : threshold; 152 const timeOriginIsReliable = timeOriginDelta < threshold; 153 154 // While performance.timing.navigationStart is deprecated in favor of performance.timeOrigin, performance.timeOrigin 155 // is not as widely supported. Namely, performance.timeOrigin is undefined in Safari as of writing. 156 // Also as of writing, performance.timing is not available in Web Workers in mainstream browsers, so it is not always 157 // a valid fallback. In the absence of an initial time provided by the browser, fallback to the current time from the 158 // Date API. 159 // eslint-disable-next-line deprecation/deprecation 160 const navigationStart = performance.timing && performance.timing.navigationStart; 161 const hasNavigationStart = typeof navigationStart === 'number'; 162 // if navigationStart isn't available set delta to threshold so it isn't used 163 const navigationStartDelta = hasNavigationStart ? Math.abs(navigationStart + performanceNow - dateNow) : threshold; 164 const navigationStartIsReliable = navigationStartDelta < threshold; 165 166 if (timeOriginIsReliable || navigationStartIsReliable) { 167 // Use the more reliable time origin 168 if (timeOriginDelta <= navigationStartDelta) { 169 _browserPerformanceTimeOriginMode = 'timeOrigin'; 170 return performance.timeOrigin; 171 } else { 172 _browserPerformanceTimeOriginMode = 'navigationStart'; 173 return navigationStart; 174 } 175 } 176 177 // Either both timeOrigin and navigationStart are skewed or neither is available, fallback to Date. 178 _browserPerformanceTimeOriginMode = 'dateNow'; 179 return dateNow; 180 })(); 181 182 export { _browserPerformanceTimeOriginMode, browserPerformanceTimeOrigin, dateTimestampInSeconds, timestampInSeconds, timestampWithMs, usingPerformanceAPI }; 183 //# sourceMappingURL=time.js.map