-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathtime.ts
132 lines (115 loc) · 5.43 KB
/
time.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { GLOBAL_OBJ } from './worldwide';
const ONE_SECOND_IN_MS = 1000;
/**
* A partial definition of the [Performance Web API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Performance}
* for accessing a high-resolution monotonic clock.
*/
interface Performance {
/**
* The millisecond timestamp at which measurement began, measured in Unix time.
*/
timeOrigin: number;
/**
* Returns the current millisecond timestamp, where 0 represents the start of measurement.
*/
now(): number;
}
/**
* Returns a timestamp in seconds since the UNIX epoch using the Date API.
*/
export function dateTimestampInSeconds(): number {
return Date.now() / ONE_SECOND_IN_MS;
}
/**
* Returns a wrapper around the native Performance API browser implementation, or undefined for browsers that do not
* support the API.
*
* Wrapping the native API works around differences in behavior from different browsers.
*/
function createUnixTimestampInSecondsFunc(): () => number {
const { performance } = GLOBAL_OBJ as typeof GLOBAL_OBJ & { performance?: Performance };
if (!performance?.now) {
return dateTimestampInSeconds;
}
// Some browser and environments don't have a timeOrigin, so we fallback to
// using Date.now() to compute the starting time.
const approxStartingTimeOrigin = Date.now() - performance.now();
const timeOrigin = performance.timeOrigin == undefined ? approxStartingTimeOrigin : performance.timeOrigin;
// performance.now() is a monotonic clock, which means it starts at 0 when the process begins. To get the current
// wall clock time (actual UNIX timestamp), we need to add the starting time origin and the current time elapsed.
//
// TODO: This does not account for the case where the monotonic clock that powers performance.now() drifts from the
// wall clock time, which causes the returned timestamp to be inaccurate. We should investigate how to detect and
// correct for this.
// See: https://github.com/getsentry/sentry-javascript/issues/2590
// See: https://github.com/mdn/content/issues/4713
// See: https://dev.to/noamr/when-a-millisecond-is-not-a-millisecond-3h6
return () => {
return (timeOrigin + performance.now()) / ONE_SECOND_IN_MS;
};
}
/**
* Returns a timestamp in seconds since the UNIX epoch using either the Performance or Date APIs, depending on the
* availability of the Performance API.
*
* BUG: Note that because of how browsers implement the Performance API, the clock might stop when the computer is
* asleep. This creates a skew between `dateTimestampInSeconds` and `timestampInSeconds`. The
* skew can grow to arbitrary amounts like days, weeks or months.
* See https://github.com/getsentry/sentry-javascript/issues/2590.
*/
export const timestampInSeconds = createUnixTimestampInSecondsFunc();
/**
* Cached result of getBrowserTimeOrigin.
*/
let cachedTimeOrigin: [number | undefined, string] | undefined;
/**
* Gets the time origin and the mode used to determine it.
*/
function getBrowserTimeOrigin(): [number | undefined, string] {
// Unfortunately browsers may report an inaccurate time origin data, through either performance.timeOrigin or
// performance.timing.navigationStart, which results in poor results in performance data. We only treat time origin
// data as reliable if they are within a reasonable threshold of the current time.
const { performance } = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;
if (!performance?.now) {
return [undefined, 'none'];
}
const threshold = 3600 * 1000;
const performanceNow = performance.now();
const dateNow = Date.now();
// if timeOrigin isn't available set delta to threshold so it isn't used
const timeOriginDelta = performance.timeOrigin
? Math.abs(performance.timeOrigin + performanceNow - dateNow)
: threshold;
const timeOriginIsReliable = timeOriginDelta < threshold;
// While performance.timing.navigationStart is deprecated in favor of performance.timeOrigin, performance.timeOrigin
// is not as widely supported. Namely, performance.timeOrigin is undefined in Safari as of writing.
// Also as of writing, performance.timing is not available in Web Workers in mainstream browsers, so it is not always
// a valid fallback. In the absence of an initial time provided by the browser, fallback to the current time from the
// Date API.
// eslint-disable-next-line deprecation/deprecation
const navigationStart = performance.timing?.navigationStart;
const hasNavigationStart = typeof navigationStart === 'number';
// if navigationStart isn't available set delta to threshold so it isn't used
const navigationStartDelta = hasNavigationStart ? Math.abs(navigationStart + performanceNow - dateNow) : threshold;
const navigationStartIsReliable = navigationStartDelta < threshold;
if (timeOriginIsReliable || navigationStartIsReliable) {
// Use the more reliable time origin
if (timeOriginDelta <= navigationStartDelta) {
return [performance.timeOrigin, 'timeOrigin'];
} else {
return [navigationStart, 'navigationStart'];
}
}
// Either both timeOrigin and navigationStart are skewed or neither is available, fallback to Date.
return [dateNow, 'dateNow'];
}
/**
* The number of milliseconds since the UNIX epoch. This value is only usable in a browser, and only when the
* performance API is available.
*/
export function browserPerformanceTimeOrigin(): number | undefined {
if (!cachedTimeOrigin) {
cachedTimeOrigin = getBrowserTimeOrigin();
}
return cachedTimeOrigin[0];
}