-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathtime.ts
More file actions
140 lines (123 loc) · 5.65 KB
/
time.ts
File metadata and controls
140 lines (123 loc) · 5.65 KB
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
133
134
135
136
137
138
139
140
import { safeDateNow, withRandomSafeContext } from './randomSafeContext';
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 safeDateNow() / 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 };
// Some browser and environments don't have a performance or timeOrigin, so we fallback to
// using Date.now() to compute the starting time.
if (!performance?.now || !performance.timeOrigin) {
return dateTimestampInSeconds;
}
const timeOrigin = 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 + withRandomSafeContext(() => performance.now())) / ONE_SECOND_IN_MS;
};
}
let _cachedTimestampInSeconds: (() => number) | undefined;
/**
* 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 function timestampInSeconds(): number {
// We store this in a closure so that we don't have to create a new function every time this is called.
const func = _cachedTimestampInSeconds ?? (_cachedTimestampInSeconds = createUnixTimestampInSecondsFunc());
return func();
}
/**
* Cached result of getBrowserTimeOrigin.
*/
let cachedTimeOrigin: number | null | undefined = null;
/**
* Gets the time origin and the mode used to determine it.
*
* 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.
*
* TODO: move to `@sentry/browser-utils` package.
*/
function getBrowserTimeOrigin(): number | undefined {
const { performance } = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;
if (!performance?.now) {
return undefined;
}
const threshold = 300_000; // 5 minutes in milliseconds
const performanceNow = withRandomSafeContext(() => performance.now());
const dateNow = safeDateNow();
const timeOrigin = performance.timeOrigin;
if (typeof timeOrigin === 'number') {
const timeOriginDelta = Math.abs(timeOrigin + performanceNow - dateNow);
if (timeOriginDelta < threshold) {
return timeOrigin;
}
}
// TODO: Remove all code related to `performance.timing.navigationStart` once we drop support for Safari 14.
// `performance.timeSince` is available in Safari 15.
// see: https://caniuse.com/mdn-api_performance_timeorigin
// 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;
if (typeof navigationStart === 'number') {
const navigationStartDelta = Math.abs(navigationStart + performanceNow - dateNow);
if (navigationStartDelta < threshold) {
return navigationStart;
}
}
// Either both timeOrigin and navigationStart are skewed or neither is available, fallback to subtracting
// `performance.now()` from `Date.now()`.
return dateNow - performanceNow;
}
/**
* 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 === null) {
cachedTimeOrigin = getBrowserTimeOrigin();
}
return cachedTimeOrigin;
}