@dash0/sdk-web 0.12.1 → 0.13.1

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.
@@ -3,4 +3,5 @@ export declare const TRACE_ID_BYTES = 16;
3
3
  export declare const PAGE_LOAD_ID_BYTES = 16;
4
4
  export declare const SESSION_ID_BYTES = 8;
5
5
  export declare const TAB_ID_BYTES = 8;
6
+ export declare const WEB_EVENT_ID_BYTES = 8;
6
7
  export declare function generateUniqueId(byteCount: number): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dash0/sdk-web",
3
- "version": "0.12.1",
3
+ "version": "0.13.1",
4
4
  "description": "Dash0's Web SDK to collect telemetry from end-users' web browsers",
5
5
  "type": "module",
6
6
  "main": "dist/dash0.umd.cjs",
@@ -1,9 +1,10 @@
1
1
  import { KeyValue } from "../../types/otlp";
2
- import { nav, NO_VALUE_FALLBACK, win } from "../utils";
2
+ import { generateUniqueId, nav, NO_VALUE_FALLBACK, WEB_EVENT_ID_BYTES, win } from "../utils";
3
3
  import {
4
4
  BROWSER_TAB_ID,
5
5
  NETWORK_CONNECTION_TYPE,
6
6
  SESSION_ID,
7
+ WEB_EVENT_ID,
7
8
  WINDOW_HEIGHT,
8
9
  WINDOW_WIDTH,
9
10
  } from "../semantic-conventions";
@@ -18,6 +19,10 @@ type Options = {
18
19
  };
19
20
 
20
21
  export function addCommonAttributes(attributes: KeyValue[], options?: Options): void {
22
+ if (attributes.find((attribute) => attribute.key === WEB_EVENT_ID) === undefined) {
23
+ addAttribute(attributes, WEB_EVENT_ID, generateUniqueId(WEB_EVENT_ID_BYTES));
24
+ }
25
+
21
26
  for (let i = 0; i < vars.signalAttributes.length; i++) {
22
27
  attributes.push(vars.signalAttributes[i]!);
23
28
  }
@@ -0,0 +1,92 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { addCommonAttributes } from "./common";
3
+ import { KeyValue } from "../../types/otlp";
4
+ import * as utils from "../utils";
5
+ import * as varsModule from "../vars";
6
+ import * as urlModule from "./url";
7
+ import * as sessionApi from "../api/session";
8
+ import * as tabIdModule from "../utils/tab-id";
9
+
10
+ describe("addCommonAttributes", () => {
11
+ beforeEach(() => {
12
+ vi.resetAllMocks();
13
+ vi.spyOn(utils, "generateUniqueId").mockReturnValue("unique-id");
14
+ vi.spyOn(urlModule, "addUrlAttributes").mockImplementation((attrs) => {
15
+ attrs.push({ key: "url", value: { stringValue: "http://test" } });
16
+ });
17
+ vi.spyOn(utils, "win", "get").mockReturnValue({
18
+ innerWidth: 800,
19
+ innerHeight: 600,
20
+ location: { href: "http://test" },
21
+ } as any);
22
+ vi.spyOn(utils, "nav", "get").mockReturnValue({ connection: { effectiveType: "4g" } } as any);
23
+ vi.spyOn(varsModule.vars, "signalAttributes", "get").mockReturnValue([
24
+ { key: "foo", value: { stringValue: "bar" } },
25
+ ]);
26
+ vi.spyOn(sessionApi, "sessionId", "get").mockReturnValue("session-123");
27
+ vi.spyOn(tabIdModule, "tabId", "get").mockReturnValue("tab-456");
28
+ });
29
+
30
+ it("adds all expected attributes", () => {
31
+ const attributes: KeyValue[] = [];
32
+ addCommonAttributes(attributes);
33
+
34
+ expect(attributes).toEqual(
35
+ expect.arrayContaining([
36
+ { key: "dash0.web.event.id", value: { stringValue: "unique-id" } },
37
+ { key: "foo", value: { stringValue: "bar" } },
38
+ { key: "url", value: { stringValue: "http://test" } },
39
+ { key: "session.id", value: { stringValue: "session-123" } },
40
+ { key: "browser.tab.id", value: { stringValue: "tab-456" } },
41
+ { key: "browser.window.width", value: { doubleValue: 800 } },
42
+ { key: "browser.window.height", value: { doubleValue: 600 } },
43
+ { key: "network.connection.subtype", value: { stringValue: "4g" } },
44
+ ])
45
+ );
46
+ });
47
+
48
+ it("does not overwrite web.event_id if already present", () => {
49
+ const attributes: KeyValue[] = [{ key: "web.event_id", value: { stringValue: "existing" } }];
50
+ addCommonAttributes(attributes);
51
+
52
+ const eventId = attributes.find((a) => a.key === "web.event_id");
53
+ expect(eventId?.value?.stringValue).toBe("existing");
54
+ });
55
+
56
+ it("should not set the session ID if sessionId is not available", () => {
57
+ vi.spyOn(sessionApi, "sessionId", "get").mockReturnValue("");
58
+ const attributes: KeyValue[] = [];
59
+ addCommonAttributes(attributes);
60
+
61
+ expect(attributes).not.toEqual(expect.arrayContaining([{ key: "session.id" }]));
62
+ });
63
+
64
+ it("should not set the tab ID if tabId is not available", () => {
65
+ vi.spyOn(tabIdModule, "tabId", "get").mockReturnValue("");
66
+ const attributes: KeyValue[] = [];
67
+ addCommonAttributes(attributes);
68
+
69
+ expect(attributes).not.toEqual(expect.arrayContaining([{ key: "browser.tab.id" }]));
70
+ });
71
+
72
+ it("should set the fallback value when window size properties are not available", () => {
73
+ vi.spyOn(utils, "win", "get").mockReturnValue(undefined);
74
+ const attributes: KeyValue[] = [];
75
+ addCommonAttributes(attributes);
76
+
77
+ expect(attributes).toEqual(
78
+ expect.arrayContaining([
79
+ { key: "browser.window.width", value: { stringValue: "undefined" } },
80
+ { key: "browser.window.height", value: { stringValue: "undefined" } },
81
+ ])
82
+ );
83
+ });
84
+
85
+ it("should not set the network connection type if not available", () => {
86
+ vi.spyOn(utils, "nav", "get").mockReturnValue({} as any);
87
+ const attributes: KeyValue[] = [];
88
+ addCommonAttributes(attributes);
89
+
90
+ expect(attributes).not.toEqual(expect.arrayContaining([{ key: "network.connection.subtype" }]));
91
+ });
92
+ });
@@ -8,6 +8,7 @@ export const DEPLOYMENT_ID = "deployment.id";
8
8
  // Misc Signal Attribute Keys
9
9
  export const EVENT_NAME = "event.name";
10
10
  export const WEB_EVENT_TITLE = "dash0.web.event.title";
11
+ export const WEB_EVENT_ID = "dash0.web.event.id";
11
12
  export const PAGE_LOAD_ID = "page.load.id";
12
13
  export const SESSION_ID = "session.id";
13
14
  export const USER_AGENT = "user_agent.original";
package/src/utils/id.ts CHANGED
@@ -3,6 +3,7 @@ export const TRACE_ID_BYTES = 16;
3
3
  export const PAGE_LOAD_ID_BYTES = TRACE_ID_BYTES;
4
4
  export const SESSION_ID_BYTES = SPAN_ID_BYTES;
5
5
  export const TAB_ID_BYTES = SPAN_ID_BYTES;
6
+ export const WEB_EVENT_ID_BYTES = SPAN_ID_BYTES;
6
7
 
7
8
  const SHARED_CHAR_CODES_ARRAY = Array(32);
8
9
  export function generateUniqueId(byteCount: number): string {
@@ -1,23 +1,29 @@
1
1
  import { KeyValue, Span, SpanStatus } from "../../../types/otlp";
2
2
  import { nowNanos } from "../time";
3
- import { SPAN_KIND_CLIENT, SPAN_STATUS_UNSET } from "../../semantic-conventions";
3
+ import { SPAN_KIND_CLIENT, SPAN_STATUS_UNSET, WEB_EVENT_ID } from "../../semantic-conventions";
4
4
  import { sessionId } from "../../api/session";
5
5
  import { generateTraceId } from "../trace-id";
6
6
  import { generateSpanId } from "../span-id";
7
+ import { addAttribute } from "./attributes";
7
8
 
8
9
  export type InProgressSpan = Omit<Span, "endTimeUnixNano">;
9
10
 
10
11
  export function startSpan(name: string): InProgressSpan {
11
12
  const traceId = generateTraceId(sessionId);
13
+ const spanId = generateSpanId(traceId);
14
+
15
+ const attributes: KeyValue[] = [];
16
+ addAttribute(attributes, WEB_EVENT_ID, spanId);
17
+
12
18
  return {
13
19
  traceId,
14
- spanId: generateSpanId(traceId),
20
+ spanId,
15
21
  name,
16
22
  // Always CLIENT for now https://github.com/open-telemetry/opentelemetry-proto/blob/ac3242b03157295e4ee9e616af53b81517b06559/opentelemetry/proto/trace/v1/trace.proto#L143-L169
17
23
  // Note: we directly define otlp here, this differs from the values used by oteljs internally.
18
24
  kind: SPAN_KIND_CLIENT,
19
25
  startTimeUnixNano: nowNanos(),
20
- attributes: [],
26
+ attributes,
21
27
  events: [],
22
28
  links: [],
23
29
  status: { code: SPAN_STATUS_UNSET },