@dash0/sdk-web 0.6.2
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.
- package/LICENSE +202 -0
- package/README.md +496 -0
- package/dist/dash0.iife.js +2 -0
- package/dist/dash0.iife.js.map +1 -0
- package/dist/dash0.js +2 -0
- package/dist/dash0.js.map +1 -0
- package/dist/dash0.umd.cjs +2 -0
- package/dist/dash0.umd.cjs.map +1 -0
- package/dist/modules/api/attributes.js +16 -0
- package/dist/modules/api/debug.js +5 -0
- package/dist/modules/api/events.js +30 -0
- package/dist/modules/api/identify.js +29 -0
- package/dist/modules/api/init.js +130 -0
- package/dist/modules/api/report-error.js +4 -0
- package/dist/modules/api/session.js +88 -0
- package/dist/modules/attributes/common.js +26 -0
- package/dist/modules/attributes/index.js +2 -0
- package/dist/modules/attributes/url.js +25 -0
- package/dist/modules/entrypoint/npm-package.js +12 -0
- package/dist/modules/entrypoint/script.js +60 -0
- package/dist/modules/instrumentations/errors/async-function-wrapping.js +105 -0
- package/dist/modules/instrumentations/errors/event-handlers.js +52 -0
- package/dist/modules/instrumentations/errors/index.js +10 -0
- package/dist/modules/instrumentations/errors/timers.js +46 -0
- package/dist/modules/instrumentations/errors/unhandled-error.js +137 -0
- package/dist/modules/instrumentations/errors/unhandled-promise-rejection.js +29 -0
- package/dist/modules/instrumentations/http/fetch.js +164 -0
- package/dist/modules/instrumentations/http/utils.js +35 -0
- package/dist/modules/instrumentations/navigation/event.js +44 -0
- package/dist/modules/instrumentations/navigation/index.js +6 -0
- package/dist/modules/instrumentations/navigation/page-load.js +83 -0
- package/dist/modules/instrumentations/navigation/page-transition.js +72 -0
- package/dist/modules/instrumentations/web-vitals.js +32 -0
- package/dist/modules/semantic-conventions.js +79 -0
- package/dist/modules/transport/batcher.js +48 -0
- package/dist/modules/transport/fetch.js +58 -0
- package/dist/modules/transport/index.js +66 -0
- package/dist/modules/utils/constants.js +2 -0
- package/dist/modules/utils/debug.js +19 -0
- package/dist/modules/utils/fn.js +3 -0
- package/dist/modules/utils/globals.js +36 -0
- package/dist/modules/utils/id.js +16 -0
- package/dist/modules/utils/id_test.js +33 -0
- package/dist/modules/utils/ignore-rules.js +48 -0
- package/dist/modules/utils/index.js +16 -0
- package/dist/modules/utils/listeners.js +18 -0
- package/dist/modules/utils/local-storage.js +19 -0
- package/dist/modules/utils/math.js +3 -0
- package/dist/modules/utils/obj.js +10 -0
- package/dist/modules/utils/on-last-chance.js +31 -0
- package/dist/modules/utils/origin.js +14 -0
- package/dist/modules/utils/otel/attributes.js +55 -0
- package/dist/modules/utils/otel/attributes_test.js +158 -0
- package/dist/modules/utils/otel/error.js +30 -0
- package/dist/modules/utils/otel/http.js +7 -0
- package/dist/modules/utils/otel/index.js +4 -0
- package/dist/modules/utils/otel/span.js +56 -0
- package/dist/modules/utils/otel/trace-context.js +60 -0
- package/dist/modules/utils/performance.js +162 -0
- package/dist/modules/utils/pick.js +17 -0
- package/dist/modules/utils/pick_test.js +104 -0
- package/dist/modules/utils/rate-limit.js +20 -0
- package/dist/modules/utils/session-storage.js +21 -0
- package/dist/modules/utils/tab-id.js +29 -0
- package/dist/modules/utils/time.js +28 -0
- package/dist/modules/utils/timers.js +62 -0
- package/dist/modules/utils/url.js +11 -0
- package/dist/modules/utils/wrap.js +22 -0
- package/dist/modules/vars.js +25 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/api/attributes.d.ts +12 -0
- package/dist/types/api/debug.d.ts +1 -0
- package/dist/types/api/events.d.ts +36 -0
- package/dist/types/api/identify.d.ts +9 -0
- package/dist/types/api/init.d.ts +31 -0
- package/dist/types/api/report-error.d.ts +2 -0
- package/dist/types/api/session.d.ts +3 -0
- package/dist/types/attributes/common.d.ts +6 -0
- package/dist/types/attributes/index.d.ts +2 -0
- package/dist/types/attributes/url.d.ts +2 -0
- package/dist/types/entrypoint/npm-package.d.ts +8 -0
- package/dist/types/entrypoint/script.d.ts +1 -0
- package/dist/types/instrumentations/errors/async-function-wrapping.d.ts +8 -0
- package/dist/types/instrumentations/errors/event-handlers.d.ts +1 -0
- package/dist/types/instrumentations/errors/index.d.ts +1 -0
- package/dist/types/instrumentations/errors/timers.d.ts +1 -0
- package/dist/types/instrumentations/errors/unhandled-error.d.ts +5 -0
- package/dist/types/instrumentations/errors/unhandled-promise-rejection.d.ts +2 -0
- package/dist/types/instrumentations/http/fetch.d.ts +1 -0
- package/dist/types/instrumentations/http/utils.d.ts +5 -0
- package/dist/types/instrumentations/navigation/event.d.ts +1 -0
- package/dist/types/instrumentations/navigation/index.d.ts +1 -0
- package/dist/types/instrumentations/navigation/page-load.d.ts +7 -0
- package/dist/types/instrumentations/navigation/page-transition.d.ts +7 -0
- package/dist/types/instrumentations/web-vitals.d.ts +1 -0
- package/dist/types/semantic-conventions.d.ts +68 -0
- package/dist/types/transport/batcher.d.ts +4 -0
- package/dist/types/transport/fetch.d.ts +1 -0
- package/dist/types/transport/index.d.ts +3 -0
- package/dist/types/utils/constants.d.ts +2 -0
- package/dist/types/utils/debug.d.ts +7 -0
- package/dist/types/utils/fn.d.ts +1 -0
- package/dist/types/utils/globals.d.ts +14 -0
- package/dist/types/utils/id.d.ts +6 -0
- package/dist/types/utils/id_test.d.ts +1 -0
- package/dist/types/utils/ignore-rules.d.ts +3 -0
- package/dist/types/utils/index.d.ts +16 -0
- package/dist/types/utils/listeners.d.ts +2 -0
- package/dist/types/utils/local-storage.d.ts +4 -0
- package/dist/types/utils/math.d.ts +1 -0
- package/dist/types/utils/obj.d.ts +5 -0
- package/dist/types/utils/on-last-chance.d.ts +5 -0
- package/dist/types/utils/origin.d.ts +1 -0
- package/dist/types/utils/otel/attributes.d.ts +9 -0
- package/dist/types/utils/otel/attributes_test.d.ts +1 -0
- package/dist/types/utils/otel/error.d.ts +24 -0
- package/dist/types/utils/otel/http.d.ts +2 -0
- package/dist/types/utils/otel/index.d.ts +4 -0
- package/dist/types/utils/otel/span.d.ts +10 -0
- package/dist/types/utils/otel/trace-context.d.ts +8 -0
- package/dist/types/utils/performance.d.ts +45 -0
- package/dist/types/utils/pick.d.ts +9 -0
- package/dist/types/utils/pick_test.d.ts +1 -0
- package/dist/types/utils/rate-limit.d.ts +5 -0
- package/dist/types/utils/session-storage.d.ts +4 -0
- package/dist/types/utils/tab-id.d.ts +8 -0
- package/dist/types/utils/time.d.ts +6 -0
- package/dist/types/utils/timers.d.ts +6 -0
- package/dist/types/utils/url.d.ts +6 -0
- package/dist/types/utils/wrap.d.ts +1 -0
- package/dist/types/vars.d.ts +123 -0
- package/package.json +124 -0
- package/src/api/attributes.ts +19 -0
- package/src/api/debug.ts +6 -0
- package/src/api/events.ts +67 -0
- package/src/api/identify.ts +43 -0
- package/src/api/init.ts +221 -0
- package/src/api/report-error.ts +6 -0
- package/src/api/session.ts +113 -0
- package/src/attributes/common.ts +42 -0
- package/src/attributes/index.ts +2 -0
- package/src/attributes/url.ts +25 -0
- package/src/entrypoint/npm-package.ts +14 -0
- package/src/entrypoint/script-tag.html +23 -0
- package/src/entrypoint/script.ts +82 -0
- package/src/instrumentations/errors/async-function-wrapping.ts +157 -0
- package/src/instrumentations/errors/event-handlers.ts +77 -0
- package/src/instrumentations/errors/index.ts +11 -0
- package/src/instrumentations/errors/timers.ts +52 -0
- package/src/instrumentations/errors/unhandled-error.ts +187 -0
- package/src/instrumentations/errors/unhandled-promise-rejection.ts +30 -0
- package/src/instrumentations/http/fetch.ts +194 -0
- package/src/instrumentations/http/utils.ts +49 -0
- package/src/instrumentations/navigation/event.ts +66 -0
- package/src/instrumentations/navigation/index.ts +7 -0
- package/src/instrumentations/navigation/page-load.ts +96 -0
- package/src/instrumentations/navigation/page-transition.ts +100 -0
- package/src/instrumentations/web-vitals.ts +37 -0
- package/src/semantic-conventions.ts +91 -0
- package/src/transport/batcher.ts +59 -0
- package/src/transport/fetch.ts +72 -0
- package/src/transport/index.ts +77 -0
- package/src/utils/constants.ts +3 -0
- package/src/utils/debug.ts +25 -0
- package/src/utils/fn.ts +3 -0
- package/src/utils/globals.ts +42 -0
- package/src/utils/id.ts +17 -0
- package/src/utils/id_test.ts +41 -0
- package/src/utils/ignore-rules.ts +59 -0
- package/src/utils/index.ts +16 -0
- package/src/utils/listeners.ts +18 -0
- package/src/utils/local-storage.ts +25 -0
- package/src/utils/math.ts +3 -0
- package/src/utils/obj.ts +12 -0
- package/src/utils/on-last-chance.ts +36 -0
- package/src/utils/origin.ts +15 -0
- package/src/utils/otel/attributes.ts +67 -0
- package/src/utils/otel/attributes_test.ts +181 -0
- package/src/utils/otel/error.ts +63 -0
- package/src/utils/otel/http.ts +9 -0
- package/src/utils/otel/index.ts +4 -0
- package/src/utils/otel/span.ts +70 -0
- package/src/utils/otel/trace-context.ts +84 -0
- package/src/utils/performance.ts +203 -0
- package/src/utils/pick.ts +19 -0
- package/src/utils/pick_test.ts +134 -0
- package/src/utils/rate-limit.ts +34 -0
- package/src/utils/session-storage.ts +27 -0
- package/src/utils/tab-id.ts +33 -0
- package/src/utils/time.ts +37 -0
- package/src/utils/timers.ts +79 -0
- package/src/utils/url.ts +12 -0
- package/src/utils/wrap.ts +33 -0
- package/src/vars.ts +169 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { doc } from "../globals";
|
|
2
|
+
import { perf } from "../globals";
|
|
3
|
+
const TRACE_PARENT_HEADER = "traceparent";
|
|
4
|
+
/**
|
|
5
|
+
* See https://www.w3.org/TR/trace-context/#traceparent-header
|
|
6
|
+
*/
|
|
7
|
+
const w3cTraceparentFormat = /^00-([a-f0-9]{32})-([a-f0-9]{16})-[0-9]{1,2}$/;
|
|
8
|
+
export function getTraceContextForPageLoad() {
|
|
9
|
+
const match = getTraceparentFromMetaElement().match(w3cTraceparentFormat) ||
|
|
10
|
+
getTraceparentFromNavigationTiming().match(w3cTraceparentFormat);
|
|
11
|
+
if (match) {
|
|
12
|
+
return {
|
|
13
|
+
traceId: match[1],
|
|
14
|
+
spanId: match[2],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
function getTraceparentFromMetaElement() {
|
|
20
|
+
return (Array.from(doc?.getElementsByTagName("meta") ?? [])
|
|
21
|
+
.find((e) => e.getAttribute("name")?.toLowerCase() === TRACE_PARENT_HEADER)
|
|
22
|
+
?.content.trim() || "");
|
|
23
|
+
}
|
|
24
|
+
function getTraceparentFromNavigationTiming() {
|
|
25
|
+
const nt = perf.getEntriesByType("navigation")[0];
|
|
26
|
+
if (!nt) {
|
|
27
|
+
return "";
|
|
28
|
+
}
|
|
29
|
+
if (!nt.serverTiming) {
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
return getTraceparentFromServerTiming(nt.serverTiming);
|
|
33
|
+
}
|
|
34
|
+
function getTraceparentFromServerTiming(serverTimings) {
|
|
35
|
+
for (const serverEntry of serverTimings) {
|
|
36
|
+
if (serverEntry.name === TRACE_PARENT_HEADER) {
|
|
37
|
+
return serverEntry.description.trim();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return "";
|
|
41
|
+
}
|
|
42
|
+
export function addTraceContextHttpHeaders(fn, ctx, span) {
|
|
43
|
+
/**
|
|
44
|
+
* W3C Traceparent header.
|
|
45
|
+
* General format is ${version}-${trace-id}-${parent-id}-${trace-flags}
|
|
46
|
+
*
|
|
47
|
+
* The only spec'd version is currently 00
|
|
48
|
+
* Trace flags are an 8 bit field of bit flags:
|
|
49
|
+
* Sampled: 00000001 - Should only be set if a definite decision to record the trace was made.
|
|
50
|
+
* If set downstream processors should also record the trace
|
|
51
|
+
* Random Trace ID: 00000010 - IF set the component guarantees that the seven right most bytes of the trace-id
|
|
52
|
+
* are randomly generated. Downstream processors are then able to rely on this for technical things like shard keys.
|
|
53
|
+
*
|
|
54
|
+
* References:
|
|
55
|
+
* https://www.w3.org/TR/trace-context-2/#traceparent-header
|
|
56
|
+
* https://www.w3.org/TR/trace-context-2/#trace-flags
|
|
57
|
+
* https://www.w3.org/TR/trace-context-2/#random-trace-id-flag
|
|
58
|
+
*/
|
|
59
|
+
fn.call(ctx, "traceparent", `00-${span.traceId}-${span.spanId}-03`);
|
|
60
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { doc, perf, win } from "./globals";
|
|
2
|
+
import { now } from "./time";
|
|
3
|
+
import { noop } from "./fn";
|
|
4
|
+
import { setTimeout } from "./timers";
|
|
5
|
+
import { addEventListener, removeEventListener } from "./listeners";
|
|
6
|
+
const TEN_MINUTES_IN_MILLIS = 1000 * 60 * 10;
|
|
7
|
+
const ONE_DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
|
|
8
|
+
export const isResourceTimingAvailable = !!(perf && perf.getEntriesByType);
|
|
9
|
+
export const isPerformanceObserverAvailable = perf && typeof win["PerformanceObserver"] === "function" && typeof perf["now"] === "function";
|
|
10
|
+
export const PerformanceTimingNames = Object.freeze({
|
|
11
|
+
CONNECT_END: "connectEnd",
|
|
12
|
+
CONNECT_START: "connectStart",
|
|
13
|
+
DECODED_BODY_SIZE: "decodedBodySize",
|
|
14
|
+
DOM_COMPLETE: "domComplete",
|
|
15
|
+
DOM_CONTENT_LOADED_EVENT_END: "domContentLoadedEventEnd",
|
|
16
|
+
DOM_CONTENT_LOADED_EVENT_START: "domContentLoadedEventStart",
|
|
17
|
+
DOM_INTERACTIVE: "domInteractive",
|
|
18
|
+
DOMAIN_LOOKUP_END: "domainLookupEnd",
|
|
19
|
+
DOMAIN_LOOKUP_START: "domainLookupStart",
|
|
20
|
+
ENCODED_BODY_SIZE: "encodedBodySize",
|
|
21
|
+
FETCH_START: "fetchStart",
|
|
22
|
+
LOAD_EVENT_END: "loadEventEnd",
|
|
23
|
+
LOAD_EVENT_START: "loadEventStart",
|
|
24
|
+
NAVIGATION_START: "navigationStart",
|
|
25
|
+
REDIRECT_END: "redirectEnd",
|
|
26
|
+
REDIRECT_START: "redirectStart",
|
|
27
|
+
REQUEST_START: "requestStart",
|
|
28
|
+
RESPONSE_END: "responseEnd",
|
|
29
|
+
RESPONSE_START: "responseStart",
|
|
30
|
+
SECURE_CONNECTION_START: "secureConnectionStart",
|
|
31
|
+
START_TIME: "startTime",
|
|
32
|
+
UNLOAD_EVENT_END: "unloadEventEnd",
|
|
33
|
+
UNLOAD_EVENT_START: "unloadEventStart",
|
|
34
|
+
});
|
|
35
|
+
export function observeResourcePerformance(opts) {
|
|
36
|
+
if (!isPerformanceObserverAvailable)
|
|
37
|
+
return observeWithoutPerformanceObserverSupport(opts.onEnd);
|
|
38
|
+
// Used to calculate the duration when no resource was found.
|
|
39
|
+
let startTime;
|
|
40
|
+
let endTime;
|
|
41
|
+
let resource;
|
|
42
|
+
// Global resources that will need to be disposed
|
|
43
|
+
let observer;
|
|
44
|
+
let fallbackNoResourceFoundTimerHandle;
|
|
45
|
+
let fallbackEndNeverCalledTimerHandle;
|
|
46
|
+
return {
|
|
47
|
+
start: onStart,
|
|
48
|
+
end: onEnd,
|
|
49
|
+
cancel: disposeGlobalResources,
|
|
50
|
+
};
|
|
51
|
+
function onStart() {
|
|
52
|
+
startTime = perf.now();
|
|
53
|
+
try {
|
|
54
|
+
const PerformanceObserver = win?.PerformanceObserver;
|
|
55
|
+
if (PerformanceObserver) {
|
|
56
|
+
observer = new PerformanceObserver(onResourceFound);
|
|
57
|
+
observer?.observe({ type: "resource" });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (_e) {
|
|
61
|
+
// Some browsers may not support the passed entryTypes and decide to throw an error.
|
|
62
|
+
// This would then result in an error with a message like:
|
|
63
|
+
//
|
|
64
|
+
// entryTypes only contained unsupported types
|
|
65
|
+
//
|
|
66
|
+
// Swallow and ignore the error. Treat it like unavailable performance observer data.
|
|
67
|
+
}
|
|
68
|
+
fallbackEndNeverCalledTimerHandle = setTimeout(disposeGlobalResources, TEN_MINUTES_IN_MILLIS);
|
|
69
|
+
}
|
|
70
|
+
function onEnd() {
|
|
71
|
+
endTime = perf.now();
|
|
72
|
+
cancelFallbackEndNeverCalledTimer();
|
|
73
|
+
if (resource || !isWaitingAcceptable()) {
|
|
74
|
+
end();
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
addEventListener(doc, "visibilitychange", onVisibilityChanged);
|
|
78
|
+
fallbackNoResourceFoundTimerHandle = setTimeout(end, opts.maxWaitForResourceMillis);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function end() {
|
|
82
|
+
disposeGlobalResources();
|
|
83
|
+
// In some old web browsers, e.g. Chrome 31, the value provided as the duration
|
|
84
|
+
// can be very wrong. We have seen cases where this value is measured in years.
|
|
85
|
+
// If this does seem be the case, then we will ignore the duration property and
|
|
86
|
+
// instead prefer our approximation.
|
|
87
|
+
if (resource?.duration && resource.duration < ONE_DAY_IN_MILLIS) {
|
|
88
|
+
opts.onEnd({ resource, duration: Math.round(resource.duration) });
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
opts.onEnd({ resource, duration: Math.round(endTime - startTime) });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function onResourceFound(list) {
|
|
95
|
+
const r = list.getEntriesByType("resource").find((e) => {
|
|
96
|
+
// This polymorphism is not properly represented in the api types. The cast is safe since we're only accessing the resource timings.
|
|
97
|
+
const entry = e;
|
|
98
|
+
return (entry.startTime >= startTime &&
|
|
99
|
+
(!endTime || endTime + opts.maxToleranceForResourceTimingsMillis >= entry.responseEnd) &&
|
|
100
|
+
opts.resourceMatcher(entry));
|
|
101
|
+
});
|
|
102
|
+
if (!r)
|
|
103
|
+
return;
|
|
104
|
+
resource = r;
|
|
105
|
+
if (endTime)
|
|
106
|
+
end();
|
|
107
|
+
}
|
|
108
|
+
function onVisibilityChanged() {
|
|
109
|
+
if (!isWaitingAcceptable())
|
|
110
|
+
end();
|
|
111
|
+
}
|
|
112
|
+
function disposeGlobalResources() {
|
|
113
|
+
disconnectResourceObserver();
|
|
114
|
+
cancelFallbackNoResourceFoundTimer();
|
|
115
|
+
cancelFallbackEndNeverCalledTimer();
|
|
116
|
+
stopVisibilityObservation();
|
|
117
|
+
}
|
|
118
|
+
function disconnectResourceObserver() {
|
|
119
|
+
if (observer) {
|
|
120
|
+
try {
|
|
121
|
+
observer?.disconnect();
|
|
122
|
+
}
|
|
123
|
+
catch (_e) {
|
|
124
|
+
// Observer disconnect may throw when connect attempt wasn't successful. Ignore this.
|
|
125
|
+
}
|
|
126
|
+
observer = undefined;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function cancelFallbackNoResourceFoundTimer() {
|
|
130
|
+
if (fallbackNoResourceFoundTimerHandle) {
|
|
131
|
+
clearTimeout(fallbackNoResourceFoundTimerHandle);
|
|
132
|
+
fallbackNoResourceFoundTimerHandle = undefined;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function cancelFallbackEndNeverCalledTimer() {
|
|
136
|
+
if (fallbackEndNeverCalledTimerHandle) {
|
|
137
|
+
clearTimeout(fallbackEndNeverCalledTimerHandle);
|
|
138
|
+
fallbackEndNeverCalledTimerHandle = undefined;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function stopVisibilityObservation() {
|
|
142
|
+
if (!doc)
|
|
143
|
+
return;
|
|
144
|
+
removeEventListener(doc, "visibilitychange", onVisibilityChanged);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// We may only wait for resource data to arrive as long as the document is visible or in the process
|
|
148
|
+
// of becoming visible. In all other cases we might lose data when waiting, e.g. when the document
|
|
149
|
+
// is in the process of being disposed.
|
|
150
|
+
function isWaitingAcceptable() {
|
|
151
|
+
return doc?.visibilityState === "visible" || doc?.visibilityState === "prerender";
|
|
152
|
+
}
|
|
153
|
+
function observeWithoutPerformanceObserverSupport(onEnd) {
|
|
154
|
+
let start = 0;
|
|
155
|
+
return {
|
|
156
|
+
start: () => {
|
|
157
|
+
start = now();
|
|
158
|
+
},
|
|
159
|
+
end: () => onEnd({ duration: now() - start }),
|
|
160
|
+
cancel: noop,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a new object by picking specified properties from the source object.
|
|
3
|
+
* Similar to TypeScript's Pick<T, K> utility type but as a runtime function.
|
|
4
|
+
*
|
|
5
|
+
* @param obj - The source object to pick properties from
|
|
6
|
+
* @param keys - Array of property keys to pick from the source object
|
|
7
|
+
* @returns A new object containing only the specified properties
|
|
8
|
+
*/
|
|
9
|
+
export function pick(obj, keys) {
|
|
10
|
+
const result = {};
|
|
11
|
+
for (const key of keys) {
|
|
12
|
+
if (key in obj) {
|
|
13
|
+
result[key] = obj[key];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { pick } from "./pick";
|
|
3
|
+
describe("pick", () => {
|
|
4
|
+
it("should pick specified properties from an object", () => {
|
|
5
|
+
const source = {
|
|
6
|
+
name: "John",
|
|
7
|
+
age: 30,
|
|
8
|
+
email: "john@example.com",
|
|
9
|
+
city: "New York",
|
|
10
|
+
};
|
|
11
|
+
const result = pick(source, ["name", "age"]);
|
|
12
|
+
expect(result).toEqual({
|
|
13
|
+
name: "John",
|
|
14
|
+
age: 30,
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
it("should return empty object when picking from empty object", () => {
|
|
18
|
+
const source = {};
|
|
19
|
+
const result = pick(source, ["name", "age"]);
|
|
20
|
+
expect(result).toEqual({});
|
|
21
|
+
});
|
|
22
|
+
it("should handle picking non-existent properties", () => {
|
|
23
|
+
const source = {
|
|
24
|
+
name: "John",
|
|
25
|
+
age: 30,
|
|
26
|
+
};
|
|
27
|
+
const result = pick(source, ["name", "nonExistent"]);
|
|
28
|
+
expect(result).toEqual({
|
|
29
|
+
name: "John",
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
it("should pick all properties when all keys are specified", () => {
|
|
33
|
+
const source = {
|
|
34
|
+
a: 1,
|
|
35
|
+
b: 2,
|
|
36
|
+
c: 3,
|
|
37
|
+
};
|
|
38
|
+
const result = pick(source, ["a", "b", "c"]);
|
|
39
|
+
expect(result).toEqual(source);
|
|
40
|
+
expect(result).not.toBe(source); // Should be a new object
|
|
41
|
+
});
|
|
42
|
+
it("should return empty object when no keys are specified", () => {
|
|
43
|
+
const source = {
|
|
44
|
+
name: "John",
|
|
45
|
+
age: 30,
|
|
46
|
+
};
|
|
47
|
+
const result = pick(source, []);
|
|
48
|
+
expect(result).toEqual({});
|
|
49
|
+
});
|
|
50
|
+
it("should preserve property values including falsy ones", () => {
|
|
51
|
+
const source = {
|
|
52
|
+
name: "",
|
|
53
|
+
age: 0,
|
|
54
|
+
active: false,
|
|
55
|
+
data: null,
|
|
56
|
+
info: undefined,
|
|
57
|
+
};
|
|
58
|
+
const result = pick(source, ["name", "age", "active", "data", "info"]);
|
|
59
|
+
expect(result).toEqual({
|
|
60
|
+
name: "",
|
|
61
|
+
age: 0,
|
|
62
|
+
active: false,
|
|
63
|
+
data: null,
|
|
64
|
+
info: undefined,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it("should work with nested objects", () => {
|
|
68
|
+
const source = {
|
|
69
|
+
user: {
|
|
70
|
+
name: "John",
|
|
71
|
+
age: 30,
|
|
72
|
+
},
|
|
73
|
+
settings: {
|
|
74
|
+
theme: "dark",
|
|
75
|
+
},
|
|
76
|
+
isActive: true,
|
|
77
|
+
};
|
|
78
|
+
const result = pick(source, ["user", "isActive"]);
|
|
79
|
+
expect(result).toEqual({
|
|
80
|
+
user: {
|
|
81
|
+
name: "John",
|
|
82
|
+
age: 30,
|
|
83
|
+
},
|
|
84
|
+
isActive: true,
|
|
85
|
+
});
|
|
86
|
+
expect(result.user).toBe(source.user); // Should reference the same nested object
|
|
87
|
+
});
|
|
88
|
+
it("should maintain type safety", () => {
|
|
89
|
+
const user = {
|
|
90
|
+
id: 1,
|
|
91
|
+
name: "John",
|
|
92
|
+
email: "john@example.com",
|
|
93
|
+
isActive: true,
|
|
94
|
+
};
|
|
95
|
+
const result = pick(user, ["id", "name"]);
|
|
96
|
+
// TypeScript should infer the type as Pick<User, 'id' | 'name'>
|
|
97
|
+
expect(result.id).toBe(1);
|
|
98
|
+
expect(result.name).toBe("John");
|
|
99
|
+
expect(result).toEqual({
|
|
100
|
+
id: 1,
|
|
101
|
+
name: "John",
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { setInterval } from "./timers";
|
|
2
|
+
export function createRateLimiter(opts) {
|
|
3
|
+
const maxCalls = opts.maxCalls || 4096;
|
|
4
|
+
const maxCallsPerTenMinutes = opts.maxCallsPerTenMinutes || 128;
|
|
5
|
+
const maxCallsPerTenSeconds = opts.maxCallsPerTenSeconds || 32;
|
|
6
|
+
let totalCalls = 0;
|
|
7
|
+
let totalCallsInLastTenMinutes = 0;
|
|
8
|
+
let totalCallsInLastTenSeconds = 0;
|
|
9
|
+
setInterval(function () {
|
|
10
|
+
totalCallsInLastTenMinutes = 0;
|
|
11
|
+
}, 1000 * 60 * 10);
|
|
12
|
+
setInterval(function () {
|
|
13
|
+
totalCallsInLastTenSeconds = 0;
|
|
14
|
+
}, 1000 * 10);
|
|
15
|
+
return function isExcessiveUsage() {
|
|
16
|
+
return (++totalCalls > maxCalls ||
|
|
17
|
+
++totalCallsInLastTenMinutes > maxCallsPerTenMinutes ||
|
|
18
|
+
++totalCallsInLastTenSeconds > maxCallsPerTenSeconds);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// sessionStorage API re-exposed to allow testing.
|
|
2
|
+
import { sessionStorage } from "./globals";
|
|
3
|
+
export const isSupported = sessionStorage != null &&
|
|
4
|
+
typeof sessionStorage.getItem === "function" &&
|
|
5
|
+
typeof sessionStorage.setItem === "function";
|
|
6
|
+
export function getItem(k) {
|
|
7
|
+
if (isSupported && sessionStorage) {
|
|
8
|
+
return sessionStorage.getItem(k);
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
export function setItem(k, v) {
|
|
13
|
+
if (isSupported && sessionStorage) {
|
|
14
|
+
sessionStorage.setItem(k, v);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function removeItem(k) {
|
|
18
|
+
if (isSupported && sessionStorage) {
|
|
19
|
+
sessionStorage.removeItem(k);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getItem, isSupported, setItem } from "./session-storage";
|
|
2
|
+
import { debug, warn } from "./debug";
|
|
3
|
+
import { generateUniqueId, TAB_ID_BYTES } from "./id";
|
|
4
|
+
const TAB_ID_STORAGE_KEY = "d0_tab";
|
|
5
|
+
export let tabId = null;
|
|
6
|
+
/**
|
|
7
|
+
* We want to be able to identify what browser tab the user is looking at. We do this by identifying tabs through
|
|
8
|
+
* random-generated IDs that are kept in session storage. To quote MDN:
|
|
9
|
+
*
|
|
10
|
+
* > sessionStorage is partitioned by both origin and browser tabs (top-level browsing contexts)
|
|
11
|
+
*/
|
|
12
|
+
export function initializeTabId() {
|
|
13
|
+
if (!isSupported) {
|
|
14
|
+
debug("Storage API is not available and tab tracking is therefore not supported.");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const storedValue = getItem(TAB_ID_STORAGE_KEY);
|
|
19
|
+
if (storedValue) {
|
|
20
|
+
tabId = storedValue;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
tabId = generateUniqueId(TAB_ID_BYTES);
|
|
24
|
+
setItem(TAB_ID_STORAGE_KEY, tabId);
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
warn("Failed to record tab ID information", e);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { perf } from "./globals";
|
|
2
|
+
export function now() {
|
|
3
|
+
return new Date().getTime();
|
|
4
|
+
}
|
|
5
|
+
export function nowNanos() {
|
|
6
|
+
const timeOrigin = getTimeOrigin();
|
|
7
|
+
if (timeOrigin) {
|
|
8
|
+
return String((perf.now() + timeOrigin) * 1000000);
|
|
9
|
+
}
|
|
10
|
+
return toNanosTs(new Date());
|
|
11
|
+
}
|
|
12
|
+
export function toNanosTs(time) {
|
|
13
|
+
if (typeof time === "object") {
|
|
14
|
+
return toNanosTs(time.getTime());
|
|
15
|
+
}
|
|
16
|
+
// We don't multiply, because we want to keep number precision
|
|
17
|
+
return String(time) + "000000";
|
|
18
|
+
}
|
|
19
|
+
export function getTimeOrigin() {
|
|
20
|
+
let timeOrigin = perf?.timeOrigin;
|
|
21
|
+
if (typeof timeOrigin !== "number") {
|
|
22
|
+
timeOrigin = perf?.timing?.fetchStart;
|
|
23
|
+
}
|
|
24
|
+
return timeOrigin;
|
|
25
|
+
}
|
|
26
|
+
export function domHRTimestampToNanos(ts) {
|
|
27
|
+
return String(Math.round((ts + getTimeOrigin()) * 1000000));
|
|
28
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { warn, debug } from "./debug";
|
|
2
|
+
import { win } from "./globals";
|
|
3
|
+
// This module contains wrappers around the standard timer API. These wrappers can be used to
|
|
4
|
+
// ensure that execution of timers happens outside of any Angular specific zones. This in turn
|
|
5
|
+
// means that this script will never disturb Angular's stabilization phase.
|
|
6
|
+
// https://angular.io/api/core/ApplicationRef#isStable
|
|
7
|
+
// Please note that it may sometimes be necessary to deliberately execute code inside of
|
|
8
|
+
// Angular's Zones. Always take care to make a deliberate decision when to use and when not to
|
|
9
|
+
// use these wrappers.
|
|
10
|
+
// We take a copy of all globals to ensure that no other script will change them all of a sudden.
|
|
11
|
+
// This ensures that when we register a timeout/interval on one global, that we will be able to
|
|
12
|
+
// de-register it again in all cases.
|
|
13
|
+
const globals = {
|
|
14
|
+
setTimeout: win?.setTimeout,
|
|
15
|
+
clearTimeout: win?.clearTimeout,
|
|
16
|
+
setInterval: win?.setInterval,
|
|
17
|
+
clearInterval: win?.clearInterval,
|
|
18
|
+
};
|
|
19
|
+
// If the globals don't exist at execution time of this file, then we know that the globals stored
|
|
20
|
+
// above are not wrapped by Zone.js. This in turn can mean better performance for Angular users.
|
|
21
|
+
export const isRunningZoneJs = win != null && win["Zone"] != null && win["Zone"]["root"] != null && typeof win["Zone"]["root"]["run"] === "function";
|
|
22
|
+
if (isRunningZoneJs) {
|
|
23
|
+
debug("Discovered Zone.js globals. Will attempt to register all timers inside the root Zone.");
|
|
24
|
+
}
|
|
25
|
+
export function setTimeout(..._) {
|
|
26
|
+
return executeGlobally.apply("setTimeout", arguments);
|
|
27
|
+
}
|
|
28
|
+
export function clearTimeout(..._) {
|
|
29
|
+
return executeGlobally.apply("clearTimeout", arguments);
|
|
30
|
+
}
|
|
31
|
+
export function setInterval(..._) {
|
|
32
|
+
return executeGlobally.apply("setInterval", arguments);
|
|
33
|
+
}
|
|
34
|
+
export function clearInterval(..._) {
|
|
35
|
+
return executeGlobally.apply("clearInterval", arguments);
|
|
36
|
+
}
|
|
37
|
+
function executeGlobally() {
|
|
38
|
+
// We don't want to incur a performance penalty for all users just because some
|
|
39
|
+
// users are relying on zone.js. This API looks quite ridiculous, but it
|
|
40
|
+
// allows for concise and efficient code, e.g. arguments does not need to be
|
|
41
|
+
// translated into an array.
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
43
|
+
const globalFunctionName = this;
|
|
44
|
+
if (isRunningZoneJs) {
|
|
45
|
+
try {
|
|
46
|
+
// Incurr a performance overhead for Zone.js users that we just cannot avoid:
|
|
47
|
+
// Copy the arguments passed in here so that we can use them inside the root
|
|
48
|
+
// zone.
|
|
49
|
+
const args = Array.prototype.slice.apply(arguments);
|
|
50
|
+
// @ts-expect-error necessary Zone.js types not added
|
|
51
|
+
return win["Zone"]["root"]["run"](globals[globalFunctionName], win, args);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
warn("Failed to execute %s inside of zone (via Zone.js). Falling back to execution inside currently " +
|
|
55
|
+
"active zone.", globalFunctionName, e);
|
|
56
|
+
// failure – maybe zone js not properly initialized? Fall back to execution
|
|
57
|
+
// outside of Zone.js as a last resort (outside of try/catch and if)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Note: Explicitly passing win as 'this' even though we are getting the function from 'globals'
|
|
61
|
+
return globals[globalFunctionName]?.apply(win, arguments);
|
|
62
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { doc, loc } from "./globals";
|
|
2
|
+
/**
|
|
3
|
+
* Parses url using URL constructor. Supports URL objects as passthrough input to simplify implementations
|
|
4
|
+
* May throw if parsing fails
|
|
5
|
+
* @param url
|
|
6
|
+
*/
|
|
7
|
+
export function parseUrl(url) {
|
|
8
|
+
if (typeof url !== "string")
|
|
9
|
+
return url;
|
|
10
|
+
return new URL(url, doc?.baseURI ?? loc?.href);
|
|
11
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { debug } from "./debug";
|
|
2
|
+
const INSTRUMENTED_BY_DASH0_SYMBOL = Symbol.for("INSTRUMENTED_BY_DASH0");
|
|
3
|
+
function isAlreadyInstrumented(objOrFunction) {
|
|
4
|
+
// @ts-expect-error -- typescript does not know about this hidden marker and we're not going to tell it 🤫
|
|
5
|
+
return objOrFunction[INSTRUMENTED_BY_DASH0_SYMBOL] === true;
|
|
6
|
+
}
|
|
7
|
+
function markAsInstrumented(objOrFunction) {
|
|
8
|
+
// @ts-expect-error -- typescript does not know about this hidden marker and we're not going to tell it 🤫
|
|
9
|
+
objOrFunction[INSTRUMENTED_BY_DASH0_SYMBOL] = true;
|
|
10
|
+
}
|
|
11
|
+
export function wrap(module, target, wrapper) {
|
|
12
|
+
const original = module[target];
|
|
13
|
+
if (!original) {
|
|
14
|
+
debug(`${String(target)} is not defined, unable to instrument`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (isAlreadyInstrumented(original)) {
|
|
18
|
+
debug(`${String(target)} has already been instrumented, skipping`);
|
|
19
|
+
}
|
|
20
|
+
markAsInstrumented(original);
|
|
21
|
+
module[target] = wrapper(original);
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// TODO use OTel types here?
|
|
2
|
+
export const vars = {
|
|
3
|
+
endpoints: [],
|
|
4
|
+
resource: {
|
|
5
|
+
attributes: [],
|
|
6
|
+
},
|
|
7
|
+
scope: {
|
|
8
|
+
name: "dash0-web-sdk",
|
|
9
|
+
version: __sdkVersion,
|
|
10
|
+
attributes: [],
|
|
11
|
+
},
|
|
12
|
+
signalAttributes: [],
|
|
13
|
+
ignoreUrls: [],
|
|
14
|
+
ignoreErrorMessages: [],
|
|
15
|
+
wrapEventHandlers: true,
|
|
16
|
+
wrapTimers: true,
|
|
17
|
+
propagateTraceHeadersCorsURLs: [],
|
|
18
|
+
maxWaitForResourceTimingsMillis: 10000,
|
|
19
|
+
maxToleranceForResourceTimingsMillis: 3000,
|
|
20
|
+
headersToCapture: [],
|
|
21
|
+
pageViewInstrumentation: {
|
|
22
|
+
trackVirtualPageViews: true,
|
|
23
|
+
includeParts: [],
|
|
24
|
+
},
|
|
25
|
+
};
|