@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,66 @@
|
|
|
1
|
+
import { newBatcher } from "./batcher";
|
|
2
|
+
import { send } from "./fetch";
|
|
3
|
+
import { vars } from "../vars";
|
|
4
|
+
import { createRateLimiter } from "../utils/rate-limit";
|
|
5
|
+
import { debug, error } from "../utils";
|
|
6
|
+
const logBatcher = newBatcher(sendLogs);
|
|
7
|
+
const spanBatcher = newBatcher(sendSpans);
|
|
8
|
+
let rateLimiter;
|
|
9
|
+
function isRateLimited() {
|
|
10
|
+
if (!rateLimiter) {
|
|
11
|
+
rateLimiter = createRateLimiter({
|
|
12
|
+
maxCalls: 8096,
|
|
13
|
+
maxCallsPerTenMinutes: 4096,
|
|
14
|
+
maxCallsPerTenSeconds: 128,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return rateLimiter();
|
|
18
|
+
}
|
|
19
|
+
export function sendLog(log) {
|
|
20
|
+
if (isRateLimited()) {
|
|
21
|
+
debug("Transport rate limit. Will not send item.", log);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
logBatcher.send(log);
|
|
25
|
+
}
|
|
26
|
+
function sendLogs(logs) {
|
|
27
|
+
send("/v1/logs", {
|
|
28
|
+
resourceLogs: [
|
|
29
|
+
{
|
|
30
|
+
resource: vars.resource,
|
|
31
|
+
scopeLogs: [
|
|
32
|
+
{
|
|
33
|
+
scope: vars.scope,
|
|
34
|
+
logRecords: logs,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
}).catch((err) => {
|
|
40
|
+
error("Failed to transmit logs", err);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
export function sendSpan(span) {
|
|
44
|
+
if (isRateLimited()) {
|
|
45
|
+
debug("Transport rate limit. Will not send item.", span);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
spanBatcher.send(span);
|
|
49
|
+
}
|
|
50
|
+
function sendSpans(spans) {
|
|
51
|
+
send("/v1/traces", {
|
|
52
|
+
resourceSpans: [
|
|
53
|
+
{
|
|
54
|
+
resource: vars.resource,
|
|
55
|
+
scopeSpans: [
|
|
56
|
+
{
|
|
57
|
+
scope: vars.scope,
|
|
58
|
+
spans: spans,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
}).catch((err) => {
|
|
64
|
+
error("Failed to transmit spans", err);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { noop } from "./fn";
|
|
2
|
+
export const log = createLogger("log");
|
|
3
|
+
export const info = createLogger("info");
|
|
4
|
+
export const warn = createLogger("warn");
|
|
5
|
+
export const error = createLogger("error");
|
|
6
|
+
export const debug = createLogger("debug");
|
|
7
|
+
function createLogger(method) {
|
|
8
|
+
if (typeof console === "undefined" || typeof console.log !== "function" || typeof console.log.apply !== "function") {
|
|
9
|
+
return noop;
|
|
10
|
+
}
|
|
11
|
+
if (console[method] && typeof console[method].apply === "function") {
|
|
12
|
+
return function () {
|
|
13
|
+
console[method].apply(console, arguments);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return function () {
|
|
17
|
+
console.log.apply(console, arguments);
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// aliasing globals for improved minification
|
|
2
|
+
// Avoid blowing up in an ssr context. It is important to check via typeof here because window might not even be declared when imported in ssr.
|
|
3
|
+
export const win = typeof window !== "undefined" ? window : undefined;
|
|
4
|
+
export const doc = win?.document;
|
|
5
|
+
export const nav = win?.navigator;
|
|
6
|
+
export const loc = typeof location !== "undefined" ? location : undefined;
|
|
7
|
+
export const perf = win?.performance || win?.webkitPerformance || win?.msPerformance || win?.mozPerformance;
|
|
8
|
+
export const encodeURIComponent = win?.encodeURIComponent;
|
|
9
|
+
export const fetch = win?.fetch;
|
|
10
|
+
export const localStorage = (function () {
|
|
11
|
+
try {
|
|
12
|
+
return win?.localStorage ?? null;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// localStorage access is not permitted in certain security modes, e.g.
|
|
16
|
+
// when cookies are completely disabled in web browsers.
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
20
|
+
export const sessionStorage = (function () {
|
|
21
|
+
try {
|
|
22
|
+
return win?.sessionStorage ?? null;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// sessionStorage access is not permitted in certain security modes, e.g.
|
|
26
|
+
// when cookies are completely disabled in web browsers.
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
})();
|
|
30
|
+
/**
|
|
31
|
+
* Exposed via this module to enable testing.
|
|
32
|
+
*/
|
|
33
|
+
export function sendBeacon(url, data) {
|
|
34
|
+
return nav?.sendBeacon(url, data) ?? false;
|
|
35
|
+
}
|
|
36
|
+
/* eslint-enable no-restricted-globals */
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const SPAN_ID_BYTES = 8;
|
|
2
|
+
export const TRACE_ID_BYTES = 16;
|
|
3
|
+
export const PAGE_LOAD_ID_BYTES = TRACE_ID_BYTES;
|
|
4
|
+
export const SESSION_ID_BYTES = TRACE_ID_BYTES;
|
|
5
|
+
export const TAB_ID_BYTES = SPAN_ID_BYTES;
|
|
6
|
+
const SHARED_CHAR_CODES_ARRAY = Array(32);
|
|
7
|
+
export function generateUniqueId(byteCount) {
|
|
8
|
+
for (let i = 0; i < byteCount * 2; i++) {
|
|
9
|
+
SHARED_CHAR_CODES_ARRAY[i] = Math.floor(Math.random() * 16) + 48;
|
|
10
|
+
// valid hex characters in the range 48-57 and 97-102
|
|
11
|
+
if (SHARED_CHAR_CODES_ARRAY[i] >= 58) {
|
|
12
|
+
SHARED_CHAR_CODES_ARRAY[i] += 39;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return String.fromCharCode.apply(null, SHARED_CHAR_CODES_ARRAY.slice(0, byteCount * 2));
|
|
16
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { expect, describe, it } from "vitest";
|
|
2
|
+
import { generateUniqueId } from "./id";
|
|
3
|
+
// Create a wrapper function for testing
|
|
4
|
+
function generateId(length) {
|
|
5
|
+
if (typeof length !== "number") {
|
|
6
|
+
throw new Error("Length must be a number");
|
|
7
|
+
}
|
|
8
|
+
if (length < 1) {
|
|
9
|
+
throw new Error("Length must be greater than 0");
|
|
10
|
+
}
|
|
11
|
+
return generateUniqueId(Math.ceil(length / 2)).substring(0, length);
|
|
12
|
+
}
|
|
13
|
+
describe("generateId", () => {
|
|
14
|
+
it("returns a string of the expected length", () => {
|
|
15
|
+
const id = generateId(10);
|
|
16
|
+
expect(id).toHaveLength(10);
|
|
17
|
+
});
|
|
18
|
+
it("returns a unique ID on each call", () => {
|
|
19
|
+
const id1 = generateId(10);
|
|
20
|
+
const id2 = generateId(10);
|
|
21
|
+
expect(id1).not.toBe(id2);
|
|
22
|
+
});
|
|
23
|
+
it("throws an error if length is less than 1", () => {
|
|
24
|
+
expect(() => generateId(0)).toThrow("Length must be greater than 0");
|
|
25
|
+
});
|
|
26
|
+
it("throws an error if length is not a number", () => {
|
|
27
|
+
expect(() => generateId("abc")).toThrow("Length must be a number");
|
|
28
|
+
});
|
|
29
|
+
it("handles large lengths without errors", () => {
|
|
30
|
+
const id = generateId(1000);
|
|
31
|
+
expect(id).toHaveLength(1000);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { vars } from "../vars";
|
|
2
|
+
export function matchesAny(regexp, s) {
|
|
3
|
+
for (let i = 0, len = regexp.length; i < len; i++) {
|
|
4
|
+
if (regexp[i].test(s)) {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const DATA_URL_PREFIX = "data:";
|
|
11
|
+
export function isUrlIgnored(url) {
|
|
12
|
+
if (!url) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
// Force string conversion. During runtime we have seen that some URLs passed into this code path aren't actually
|
|
16
|
+
// strings. Reason currently unknown.
|
|
17
|
+
url = String(url);
|
|
18
|
+
if (!url) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
// We never want to track data URLs. Instead of matching these via regular expressions (which might be expensive),
|
|
22
|
+
// we are explicitly doing a startsWith ignore case check
|
|
23
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
|
|
24
|
+
if (url.substring == null || url.substring(0, DATA_URL_PREFIX.length).toLowerCase() === DATA_URL_PREFIX) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
// Disable monitoring of data transmission requests. The data transmission strategy already ensures
|
|
28
|
+
// that data transmission requests are not picked up internally. However, we have seen some users
|
|
29
|
+
// leverage custom (broken) XMLHttpRequest instrumentation to implement application code which
|
|
30
|
+
// then break the detection of data transmission requests.
|
|
31
|
+
if (isTransportRequest(url)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
return matchesAny(vars.ignoreUrls, url);
|
|
35
|
+
}
|
|
36
|
+
function isTransportRequest(url) {
|
|
37
|
+
const lowerCaseUrl = url.toLowerCase();
|
|
38
|
+
for (let i = 0, len = vars.endpoints.length; i < len; i++) {
|
|
39
|
+
const endpoint = vars.endpoints[i];
|
|
40
|
+
if (lowerCaseUrl.startsWith(endpoint.url)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
export function isErrorMessageIgnored(message) {
|
|
47
|
+
return !message || matchesAny(vars.ignoreErrorMessages, message);
|
|
48
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export * from "./debug";
|
|
2
|
+
export * from "./obj";
|
|
3
|
+
export * from "./fn";
|
|
4
|
+
export * from "./globals";
|
|
5
|
+
export * from "./id";
|
|
6
|
+
export * from "./listeners";
|
|
7
|
+
export * from "./local-storage";
|
|
8
|
+
export * from "./performance";
|
|
9
|
+
export * from "./rate-limit";
|
|
10
|
+
export * from "./time";
|
|
11
|
+
export * from "./constants";
|
|
12
|
+
export * from "./math";
|
|
13
|
+
export * from "./origin";
|
|
14
|
+
export * from "./url";
|
|
15
|
+
export * from "./pick";
|
|
16
|
+
export * from "./wrap";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// aliasing the global function for improved minification and
|
|
2
|
+
// protection against hasOwnProperty overrides.
|
|
3
|
+
export function addEventListener(target, eventType, callback) {
|
|
4
|
+
if (target.addEventListener) {
|
|
5
|
+
target.addEventListener(eventType, callback, false);
|
|
6
|
+
}
|
|
7
|
+
else if (target.attachEvent) {
|
|
8
|
+
target.attachEvent("on" + eventType, callback);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function removeEventListener(target, eventType, callback) {
|
|
12
|
+
if (target.removeEventListener) {
|
|
13
|
+
target.removeEventListener(eventType, callback, false);
|
|
14
|
+
}
|
|
15
|
+
else if (target.detachEvent) {
|
|
16
|
+
target.detachEvent("on" + eventType, callback);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// localStorage API re-exposed to allow testing.
|
|
2
|
+
import { localStorage } from "./globals";
|
|
3
|
+
export const isSupported = localStorage != null && typeof localStorage.getItem === "function" && typeof localStorage.setItem === "function";
|
|
4
|
+
export function getItem(k) {
|
|
5
|
+
if (isSupported && localStorage) {
|
|
6
|
+
return localStorage.getItem(k);
|
|
7
|
+
}
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
export function setItem(k, v) {
|
|
11
|
+
if (isSupported && localStorage) {
|
|
12
|
+
localStorage.setItem(k, v);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function removeItem(k) {
|
|
16
|
+
if (isSupported && localStorage) {
|
|
17
|
+
localStorage.removeItem(k);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const globalHasOwnProperty = Object.prototype.hasOwnProperty;
|
|
2
|
+
/**
|
|
3
|
+
* protection against hasOwnProperty overrides.
|
|
4
|
+
*/
|
|
5
|
+
export function hasOwnProperty(obj, key) {
|
|
6
|
+
return globalHasOwnProperty.call(obj, key);
|
|
7
|
+
}
|
|
8
|
+
export function hasKey(obj, key) {
|
|
9
|
+
return key in obj;
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { addEventListener } from "./listeners";
|
|
2
|
+
import { doc, win } from "./globals";
|
|
3
|
+
let isUnloading = false;
|
|
4
|
+
/**
|
|
5
|
+
* Triggers the `fn` when the HTML document is getting unloaded
|
|
6
|
+
* (or when it looks like it might be). Useful to flush batch activities.
|
|
7
|
+
*/
|
|
8
|
+
export function onLastChance(fn) {
|
|
9
|
+
if (isUnloading) {
|
|
10
|
+
fn();
|
|
11
|
+
}
|
|
12
|
+
if (!doc || !win)
|
|
13
|
+
return;
|
|
14
|
+
addEventListener(doc, "visibilitychange", function () {
|
|
15
|
+
if (doc.visibilityState !== "visible") {
|
|
16
|
+
fn();
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
addEventListener(win, "pagehide", function () {
|
|
20
|
+
isUnloading = true;
|
|
21
|
+
fn();
|
|
22
|
+
});
|
|
23
|
+
// According to the spec visibilitychange should be a replacement for
|
|
24
|
+
// beforeunload, but the reality is different (as of 2019-04-17). Chrome will
|
|
25
|
+
// close tabs without firing visibilitychange. beforeunload on the other hand
|
|
26
|
+
// is fired.
|
|
27
|
+
addEventListener(win, "beforeunload", function () {
|
|
28
|
+
isUnloading = true;
|
|
29
|
+
fn();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { parseUrl } from "./url";
|
|
2
|
+
/** Returns the origin if present (if in browser context). */
|
|
3
|
+
function getOrigin() {
|
|
4
|
+
return typeof location !== "undefined" ? location.origin : undefined;
|
|
5
|
+
}
|
|
6
|
+
export function isSameOrigin(url) {
|
|
7
|
+
try {
|
|
8
|
+
const parsedUrl = parseUrl(url);
|
|
9
|
+
return parsedUrl.origin === getOrigin();
|
|
10
|
+
}
|
|
11
|
+
catch (_e) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const ANY_VALUE_KEYS = [
|
|
2
|
+
"stringValue",
|
|
3
|
+
"boolValue",
|
|
4
|
+
"intValue",
|
|
5
|
+
"doubleValue",
|
|
6
|
+
"arrayValue",
|
|
7
|
+
"kvlistValue",
|
|
8
|
+
"bytesValue",
|
|
9
|
+
];
|
|
10
|
+
function isAnyValue(value) {
|
|
11
|
+
if (value == null || typeof value !== "object")
|
|
12
|
+
return false;
|
|
13
|
+
const keys = Object.keys(value);
|
|
14
|
+
return keys.length === 1 && ANY_VALUE_KEYS.includes(keys[0]);
|
|
15
|
+
}
|
|
16
|
+
export function toAnyValue(value) {
|
|
17
|
+
if (value == null) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
let anyValue = {};
|
|
21
|
+
if (Array.isArray(value)) {
|
|
22
|
+
anyValue["arrayValue"] = { values: value.map((e) => toAnyValue(e)) };
|
|
23
|
+
}
|
|
24
|
+
else if (typeof value === "string") {
|
|
25
|
+
anyValue["stringValue"] = value;
|
|
26
|
+
}
|
|
27
|
+
else if (typeof value === "number") {
|
|
28
|
+
anyValue["doubleValue"] = value;
|
|
29
|
+
}
|
|
30
|
+
else if (typeof value === "boolean") {
|
|
31
|
+
anyValue["boolValue"] = value;
|
|
32
|
+
}
|
|
33
|
+
else if (isAnyValue(value)) {
|
|
34
|
+
anyValue = value;
|
|
35
|
+
}
|
|
36
|
+
else if (typeof value === "object") {
|
|
37
|
+
anyValue["kvlistValue"] = { values: Object.entries(value).map(([key, value]) => toKeyValue(key, value)) };
|
|
38
|
+
}
|
|
39
|
+
return anyValue;
|
|
40
|
+
}
|
|
41
|
+
export function toKeyValue(key, value) {
|
|
42
|
+
return {
|
|
43
|
+
key: key,
|
|
44
|
+
value: toAnyValue(value),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function addAttribute(attributes, key, value) {
|
|
48
|
+
attributes.push(toKeyValue(key, value));
|
|
49
|
+
}
|
|
50
|
+
export function removeAttribute(attributes, key) {
|
|
51
|
+
const index = attributes.findIndex((attr) => attr["key"] === key);
|
|
52
|
+
if (index !== -1) {
|
|
53
|
+
attributes.splice(index, 1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { expect, describe, it } from "vitest";
|
|
2
|
+
import { toAnyValue } from "./attributes";
|
|
3
|
+
describe("toAnyValue", () => {
|
|
4
|
+
describe("primitive values", () => {
|
|
5
|
+
it("converts string values to stringValue", () => {
|
|
6
|
+
const result = toAnyValue("hello");
|
|
7
|
+
expect(result).toEqual({ stringValue: "hello" });
|
|
8
|
+
});
|
|
9
|
+
it("converts number values to doubleValue", () => {
|
|
10
|
+
const result = toAnyValue(42);
|
|
11
|
+
expect(result).toEqual({ doubleValue: 42 });
|
|
12
|
+
});
|
|
13
|
+
it("converts boolean true to boolValue", () => {
|
|
14
|
+
const result = toAnyValue(true);
|
|
15
|
+
expect(result).toEqual({ boolValue: true });
|
|
16
|
+
});
|
|
17
|
+
it("converts boolean false to boolValue", () => {
|
|
18
|
+
const result = toAnyValue(false);
|
|
19
|
+
expect(result).toEqual({ boolValue: false });
|
|
20
|
+
});
|
|
21
|
+
it("converts zero to doubleValue", () => {
|
|
22
|
+
const result = toAnyValue(0);
|
|
23
|
+
expect(result).toEqual({ doubleValue: 0 });
|
|
24
|
+
});
|
|
25
|
+
it("converts negative numbers to doubleValue", () => {
|
|
26
|
+
const result = toAnyValue(-123.45);
|
|
27
|
+
expect(result).toEqual({ doubleValue: -123.45 });
|
|
28
|
+
});
|
|
29
|
+
it("converts empty string to stringValue", () => {
|
|
30
|
+
const result = toAnyValue("");
|
|
31
|
+
expect(result).toEqual({ stringValue: "" });
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe("array values", () => {
|
|
35
|
+
it("converts array of strings to arrayValue", () => {
|
|
36
|
+
const result = toAnyValue(["a", "b", "c"]);
|
|
37
|
+
expect(result).toEqual({
|
|
38
|
+
arrayValue: {
|
|
39
|
+
values: [{ stringValue: "a" }, { stringValue: "b" }, { stringValue: "c" }],
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
it("converts array of numbers to arrayValue", () => {
|
|
44
|
+
const result = toAnyValue([1, 2, 3]);
|
|
45
|
+
expect(result).toEqual({
|
|
46
|
+
arrayValue: {
|
|
47
|
+
values: [{ doubleValue: 1 }, { doubleValue: 2 }, { doubleValue: 3 }],
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
it("converts array of booleans to arrayValue", () => {
|
|
52
|
+
const result = toAnyValue([true, false]);
|
|
53
|
+
expect(result).toEqual({
|
|
54
|
+
arrayValue: {
|
|
55
|
+
values: [{ boolValue: true }, { boolValue: false }],
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
it("converts mixed array to arrayValue", () => {
|
|
60
|
+
const result = toAnyValue([1, "hello", true]);
|
|
61
|
+
expect(result).toEqual({
|
|
62
|
+
arrayValue: {
|
|
63
|
+
values: [{ doubleValue: 1 }, { stringValue: "hello" }, { boolValue: true }],
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
it("converts empty array to arrayValue", () => {
|
|
68
|
+
const result = toAnyValue([]);
|
|
69
|
+
expect(result).toEqual({
|
|
70
|
+
arrayValue: {
|
|
71
|
+
values: [],
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe("object values", () => {
|
|
77
|
+
it("converts simple object to kvlistValue", () => {
|
|
78
|
+
const result = toAnyValue({ name: "John", age: 30 });
|
|
79
|
+
expect(result).toEqual({
|
|
80
|
+
kvlistValue: {
|
|
81
|
+
values: [
|
|
82
|
+
{ key: "name", value: { stringValue: "John" } },
|
|
83
|
+
{ key: "age", value: { doubleValue: 30 } },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
it("converts empty object to kvlistValue", () => {
|
|
89
|
+
const result = toAnyValue({});
|
|
90
|
+
expect(result).toEqual({
|
|
91
|
+
kvlistValue: {
|
|
92
|
+
values: [],
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
it("converts object with mixed value types", () => {
|
|
97
|
+
const result = toAnyValue({
|
|
98
|
+
str: "text",
|
|
99
|
+
num: 42,
|
|
100
|
+
bool: true,
|
|
101
|
+
});
|
|
102
|
+
expect(result).toEqual({
|
|
103
|
+
kvlistValue: {
|
|
104
|
+
values: [
|
|
105
|
+
{ key: "str", value: { stringValue: "text" } },
|
|
106
|
+
{ key: "num", value: { doubleValue: 42 } },
|
|
107
|
+
{ key: "bool", value: { boolValue: true } },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
describe("AnyValue passthrough", () => {
|
|
114
|
+
it("returns existing AnyValue unchanged", () => {
|
|
115
|
+
const anyValue = { stringValue: "test" };
|
|
116
|
+
const result = toAnyValue(anyValue);
|
|
117
|
+
expect(result).toBe(anyValue);
|
|
118
|
+
});
|
|
119
|
+
it("returns existing doubleValue AnyValue unchanged", () => {
|
|
120
|
+
const anyValue = { doubleValue: 123 };
|
|
121
|
+
const result = toAnyValue(anyValue);
|
|
122
|
+
expect(result).toBe(anyValue);
|
|
123
|
+
});
|
|
124
|
+
it("returns existing boolValue AnyValue unchanged", () => {
|
|
125
|
+
const anyValue = { boolValue: false };
|
|
126
|
+
const result = toAnyValue(anyValue);
|
|
127
|
+
expect(result).toBe(anyValue);
|
|
128
|
+
});
|
|
129
|
+
it("returns existing arrayValue AnyValue unchanged", () => {
|
|
130
|
+
const anyValue = {
|
|
131
|
+
arrayValue: {
|
|
132
|
+
values: [{ stringValue: "test" }],
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
const result = toAnyValue(anyValue);
|
|
136
|
+
expect(result).toBe(anyValue);
|
|
137
|
+
});
|
|
138
|
+
it("returns existing kvlistValue AnyValue unchanged", () => {
|
|
139
|
+
const anyValue = {
|
|
140
|
+
kvlistValue: {
|
|
141
|
+
values: [{ key: "test", value: { stringValue: "value" } }],
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
const result = toAnyValue(anyValue);
|
|
145
|
+
expect(result).toBe(anyValue);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
describe("undefined and null handling", () => {
|
|
149
|
+
it("returns undefined for undefined", () => {
|
|
150
|
+
const result = toAnyValue(undefined);
|
|
151
|
+
expect(result).toBeUndefined();
|
|
152
|
+
});
|
|
153
|
+
it("returns undefined for null", () => {
|
|
154
|
+
const result = toAnyValue(null);
|
|
155
|
+
expect(result).toBeUndefined();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { addSpanEvent } from "./span";
|
|
2
|
+
import { addAttribute } from "./attributes";
|
|
3
|
+
import { SPAN_EVENT_NAME_EXCEPTION, EXCEPTION_MESSAGE, EXCEPTION_STACKTRACE, EXCEPTION_TYPE, SPAN_STATUS_ERROR, } from "../../semantic-conventions";
|
|
4
|
+
export function recordException(span, exception) {
|
|
5
|
+
const attributes = [];
|
|
6
|
+
if (typeof exception === "string") {
|
|
7
|
+
addAttribute(attributes, EXCEPTION_MESSAGE, exception);
|
|
8
|
+
}
|
|
9
|
+
else if (exception) {
|
|
10
|
+
if (exception.code) {
|
|
11
|
+
addAttribute(attributes, EXCEPTION_TYPE, exception.code.toString());
|
|
12
|
+
}
|
|
13
|
+
else if (exception.name) {
|
|
14
|
+
addAttribute(attributes, EXCEPTION_TYPE, exception.name);
|
|
15
|
+
}
|
|
16
|
+
if (exception.message) {
|
|
17
|
+
addAttribute(attributes, EXCEPTION_MESSAGE, exception.message);
|
|
18
|
+
}
|
|
19
|
+
if (exception.stack) {
|
|
20
|
+
addAttribute(attributes, EXCEPTION_STACKTRACE, exception.stack);
|
|
21
|
+
}
|
|
22
|
+
addSpanEvent(span, SPAN_EVENT_NAME_EXCEPTION, attributes);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function errorToSpanStatus(e) {
|
|
26
|
+
return {
|
|
27
|
+
code: SPAN_STATUS_ERROR,
|
|
28
|
+
message: e && typeof e === "object" && "message" in e ? e.message : String(e),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { HTTP_REQUEST_HEADER, HTTP_RESPONSE_HEADER } from "../../semantic-conventions";
|
|
2
|
+
export function httpRequestHeaderKey(key) {
|
|
3
|
+
return `${HTTP_REQUEST_HEADER}.${key.toLowerCase()}`;
|
|
4
|
+
}
|
|
5
|
+
export function httpResponseHeaderKey(key) {
|
|
6
|
+
return `${HTTP_RESPONSE_HEADER}.${key.toLowerCase()}`;
|
|
7
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { nowNanos } from "../time";
|
|
2
|
+
import { generateUniqueId, SPAN_ID_BYTES, TRACE_ID_BYTES } from "../id";
|
|
3
|
+
import { SPAN_KIND_CLIENT, SPAN_STATUS_UNSET } from "../../semantic-conventions";
|
|
4
|
+
export function startSpan(name) {
|
|
5
|
+
return {
|
|
6
|
+
traceId: generateUniqueId(TRACE_ID_BYTES),
|
|
7
|
+
spanId: generateUniqueId(SPAN_ID_BYTES),
|
|
8
|
+
name,
|
|
9
|
+
// Always CLIENT for now https://github.com/open-telemetry/opentelemetry-proto/blob/ac3242b03157295e4ee9e616af53b81517b06559/opentelemetry/proto/trace/v1/trace.proto#L143-L169
|
|
10
|
+
// Note: we directly define otlp here, this differs from the values used by oteljs internally.
|
|
11
|
+
kind: SPAN_KIND_CLIENT,
|
|
12
|
+
startTimeUnixNano: nowNanos(),
|
|
13
|
+
attributes: [],
|
|
14
|
+
events: [],
|
|
15
|
+
links: [],
|
|
16
|
+
status: { code: SPAN_STATUS_UNSET },
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function endSpan(span, status, durationNano) {
|
|
20
|
+
// We cast here to avoid having to instantiate a copy of the span
|
|
21
|
+
const s = span;
|
|
22
|
+
if (status) {
|
|
23
|
+
s.status = status;
|
|
24
|
+
}
|
|
25
|
+
s.endTimeUnixNano =
|
|
26
|
+
durationNano != null ? String(Math.round(parseInt(s.startTimeUnixNano) + durationNano)) : nowNanos();
|
|
27
|
+
return s;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Adds an event to a span. Can optionally accept the events timestamp and attributes for the event.
|
|
31
|
+
* The timestamp needs to be specified as nanoseconds since unix epoch in string format.
|
|
32
|
+
*/
|
|
33
|
+
export function addSpanEvent(span, name, attributesOrTs, attributes) {
|
|
34
|
+
let ts = undefined;
|
|
35
|
+
let attr = undefined;
|
|
36
|
+
if (typeof attributesOrTs === "string") {
|
|
37
|
+
ts = attributesOrTs;
|
|
38
|
+
}
|
|
39
|
+
else if (Array.isArray(attributesOrTs)) {
|
|
40
|
+
attr = attributesOrTs;
|
|
41
|
+
}
|
|
42
|
+
if (attributes) {
|
|
43
|
+
attr = attributes;
|
|
44
|
+
}
|
|
45
|
+
span.events.push({
|
|
46
|
+
name,
|
|
47
|
+
timeUnixNano: ts != null ? ts : nowNanos(),
|
|
48
|
+
attributes: attr ?? [],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export function setSpanStatus(span, code, message) {
|
|
52
|
+
span.status = {
|
|
53
|
+
code,
|
|
54
|
+
message,
|
|
55
|
+
};
|
|
56
|
+
}
|