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