@dash0/sdk-web 0.18.3 → 0.18.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,10 +4,19 @@ export function now() {
4
4
  }
5
5
  export function nowNanos() {
6
6
  const timeOrigin = getTimeOrigin();
7
+ const currentDate = new Date();
7
8
  if (timeOrigin) {
8
- return String((perf.now() + timeOrigin) * 1000000);
9
+ const perfTime = perf.now() + timeOrigin;
10
+ const dateTime = currentDate.getTime();
11
+ // Only return perf time measurement if it hasn't significantly drifted (60s).
12
+ // Some browsers violate the performance api spec and do not tick the monotonic time while the OS sleeps or the browser hangs
13
+ // See https://bugzil.la/1709767
14
+ // See https://webkit.org/b/225610
15
+ if (Math.abs(perfTime - dateTime) < 60000) {
16
+ return String(perfTime * 1000000);
17
+ }
9
18
  }
10
- return toNanosTs(new Date());
19
+ return toNanosTs(currentDate);
11
20
  }
12
21
  export function toNanosTs(time) {
13
22
  if (typeof time === "object") {
@@ -0,0 +1,232 @@
1
+ import { expect, describe, it, vi, beforeEach, afterEach } from "vitest";
2
+ import { now, nowNanos, toNanosTs, getTimeOrigin, domHRTimestampToNanos } from "./time";
3
+ import * as globals from "./globals";
4
+ describe("now", () => {
5
+ it("returns the current time in milliseconds", () => {
6
+ const before = new Date().getTime();
7
+ const result = now();
8
+ const after = new Date().getTime();
9
+ expect(result).toBeGreaterThanOrEqual(before);
10
+ expect(result).toBeLessThanOrEqual(after);
11
+ });
12
+ });
13
+ describe("toNanosTs", () => {
14
+ it("converts milliseconds to nanoseconds as string", () => {
15
+ const ms = 1609459200000; // 2021-01-01 00:00:00 UTC
16
+ const result = toNanosTs(ms);
17
+ expect(result).toBe("1609459200000000000");
18
+ });
19
+ it("converts Date object to nanoseconds as string", () => {
20
+ const date = new Date(1609459200000);
21
+ const result = toNanosTs(date);
22
+ expect(result).toBe("1609459200000000000");
23
+ });
24
+ it("handles zero timestamp", () => {
25
+ const result = toNanosTs(0);
26
+ expect(result).toBe("0000000");
27
+ });
28
+ it("preserves precision", () => {
29
+ const ms = 1234567890123;
30
+ const result = toNanosTs(ms);
31
+ expect(result).toBe("1234567890123000000");
32
+ });
33
+ });
34
+ describe("getTimeOrigin", () => {
35
+ afterEach(() => {
36
+ vi.restoreAllMocks();
37
+ });
38
+ it("returns timeOrigin when available", () => {
39
+ const mockPerf = {
40
+ timeOrigin: 1609459200000,
41
+ now: vi.fn(),
42
+ };
43
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
44
+ const result = getTimeOrigin();
45
+ expect(result).toBe(1609459200000);
46
+ });
47
+ it("falls back to timing.fetchStart when timeOrigin is not a number", () => {
48
+ const mockPerf = {
49
+ timing: {
50
+ fetchStart: 1609459200000,
51
+ },
52
+ now: vi.fn(),
53
+ };
54
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
55
+ const result = getTimeOrigin();
56
+ expect(result).toBe(1609459200000);
57
+ });
58
+ it("returns undefined when perf is not available", () => {
59
+ vi.spyOn(globals, "perf", "get").mockReturnValue(undefined);
60
+ const result = getTimeOrigin();
61
+ expect(result).toBeUndefined();
62
+ });
63
+ it("returns undefined when both timeOrigin and fetchStart are unavailable", () => {
64
+ const mockPerf = {
65
+ now: vi.fn(),
66
+ };
67
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
68
+ const result = getTimeOrigin();
69
+ expect(result).toBeUndefined();
70
+ });
71
+ });
72
+ describe("nowNanos", () => {
73
+ let dateNowSpy;
74
+ beforeEach(() => {
75
+ dateNowSpy = vi.spyOn(Date.prototype, "getTime");
76
+ });
77
+ afterEach(() => {
78
+ vi.restoreAllMocks();
79
+ });
80
+ it("uses performance time when available and not drifted", () => {
81
+ const timeOrigin = 1609459200000;
82
+ const perfNow = 5000; // 5 seconds after time origin
83
+ const expectedTime = timeOrigin + perfNow; // 1609459205000
84
+ const mockPerf = {
85
+ timeOrigin,
86
+ now: vi.fn().mockReturnValue(perfNow),
87
+ };
88
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
89
+ dateNowSpy.mockReturnValue(expectedTime);
90
+ const result = nowNanos();
91
+ const expected = String(expectedTime * 1000000);
92
+ expect(result).toBe(expected);
93
+ });
94
+ it("falls back to Date when drift is exactly 60000ms (60 seconds)", () => {
95
+ const timeOrigin = 1609459200000;
96
+ const perfNow = 5000;
97
+ const perfTime = timeOrigin + perfNow; // 1609459205000
98
+ const dateTime = perfTime + 60000; // 60000ms drift
99
+ const mockPerf = {
100
+ timeOrigin,
101
+ now: vi.fn().mockReturnValue(perfNow),
102
+ };
103
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
104
+ dateNowSpy.mockReturnValue(dateTime);
105
+ const result = nowNanos();
106
+ const expected = String(dateTime) + "000000";
107
+ expect(result).toBe(expected);
108
+ });
109
+ it("falls back to Date when drift exceeds 60000ms (positive)", () => {
110
+ const timeOrigin = 1609459200000;
111
+ const perfNow = 5000;
112
+ const perfTime = timeOrigin + perfNow;
113
+ const dateTime = perfTime + 65000; // 65000ms drift (browser slept)
114
+ const mockPerf = {
115
+ timeOrigin,
116
+ now: vi.fn().mockReturnValue(perfNow),
117
+ };
118
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
119
+ dateNowSpy.mockReturnValue(dateTime);
120
+ const result = nowNanos();
121
+ const expected = String(dateTime) + "000000";
122
+ expect(result).toBe(expected);
123
+ });
124
+ it("falls back to Date when drift exceeds 60000ms (negative)", () => {
125
+ const timeOrigin = 1609459200000;
126
+ const perfNow = 5000;
127
+ const perfTime = timeOrigin + perfNow;
128
+ const dateTime = perfTime - 65000; // -65000ms drift (clock adjustment)
129
+ const mockPerf = {
130
+ timeOrigin,
131
+ now: vi.fn().mockReturnValue(perfNow),
132
+ };
133
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
134
+ dateNowSpy.mockReturnValue(dateTime);
135
+ const result = nowNanos();
136
+ const expected = String(dateTime) + "000000";
137
+ expect(result).toBe(expected);
138
+ });
139
+ it("uses performance time when drift is just under 60000ms (59999ms)", () => {
140
+ const timeOrigin = 1609459200000;
141
+ const perfNow = 5000;
142
+ const perfTime = timeOrigin + perfNow;
143
+ const dateTime = perfTime + 59999; // 59999ms drift (still acceptable)
144
+ const mockPerf = {
145
+ timeOrigin,
146
+ now: vi.fn().mockReturnValue(perfNow),
147
+ };
148
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
149
+ dateNowSpy.mockReturnValue(dateTime);
150
+ const result = nowNanos();
151
+ const expected = String(perfTime * 1000000);
152
+ expect(result).toBe(expected);
153
+ });
154
+ it("uses performance time when drift is just under -60000ms (-59999ms)", () => {
155
+ const timeOrigin = 1609459200000;
156
+ const perfNow = 5000;
157
+ const perfTime = timeOrigin + perfNow;
158
+ const dateTime = perfTime - 59999; // -59999ms drift (still acceptable)
159
+ const mockPerf = {
160
+ timeOrigin,
161
+ now: vi.fn().mockReturnValue(perfNow),
162
+ };
163
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
164
+ dateNowSpy.mockReturnValue(dateTime);
165
+ const result = nowNanos();
166
+ const expected = String(perfTime * 1000000);
167
+ expect(result).toBe(expected);
168
+ });
169
+ it("falls back to Date when timeOrigin is not available", () => {
170
+ vi.spyOn(globals, "perf", "get").mockReturnValue(undefined);
171
+ const dateTime = 1609459205000;
172
+ dateNowSpy.mockReturnValue(dateTime);
173
+ const result = nowNanos();
174
+ const expected = String(dateTime) + "000000";
175
+ expect(result).toBe(expected);
176
+ });
177
+ it("falls back to Date when timeOrigin is not a number", () => {
178
+ const mockPerf = {
179
+ timeOrigin: undefined,
180
+ now: vi.fn().mockReturnValue(5000),
181
+ timing: {}, // no fetchStart either
182
+ };
183
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
184
+ const dateTime = 1609459205000;
185
+ dateNowSpy.mockReturnValue(dateTime);
186
+ const result = nowNanos();
187
+ const expected = String(dateTime) + "000000";
188
+ expect(result).toBe(expected);
189
+ });
190
+ });
191
+ describe("domHRTimestampToNanos", () => {
192
+ afterEach(() => {
193
+ vi.restoreAllMocks();
194
+ });
195
+ it("converts DOM high resolution timestamp to nanoseconds", () => {
196
+ const timeOrigin = 1609459200000;
197
+ const hrTimestamp = 5000.123; // 5.000123 seconds after time origin
198
+ const mockPerf = {
199
+ timeOrigin,
200
+ now: vi.fn(),
201
+ };
202
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
203
+ const result = domHRTimestampToNanos(hrTimestamp);
204
+ const expectedMs = timeOrigin + hrTimestamp;
205
+ const expectedNanos = String(Math.round(expectedMs * 1000000));
206
+ expect(result).toBe(expectedNanos);
207
+ });
208
+ it("rounds to nearest nanosecond", () => {
209
+ const timeOrigin = 1000;
210
+ const hrTimestamp = 0.0000001; // Very small fraction
211
+ const mockPerf = {
212
+ timeOrigin,
213
+ now: vi.fn(),
214
+ };
215
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
216
+ const result = domHRTimestampToNanos(hrTimestamp);
217
+ const expectedMs = timeOrigin + hrTimestamp;
218
+ const expectedNanos = String(Math.round(expectedMs * 1000000));
219
+ expect(result).toBe(expectedNanos);
220
+ });
221
+ it("handles zero timestamp", () => {
222
+ const timeOrigin = 1609459200000;
223
+ const mockPerf = {
224
+ timeOrigin,
225
+ now: vi.fn(),
226
+ };
227
+ vi.spyOn(globals, "perf", "get").mockReturnValue(mockPerf);
228
+ const result = domHRTimestampToNanos(0);
229
+ const expectedNanos = String(Math.round(timeOrigin * 1000000));
230
+ expect(result).toBe(expectedNanos);
231
+ });
232
+ });