@dash0/sdk-web 0.10.0 → 0.11.0

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.
@@ -1,6 +1,7 @@
1
- import { isSupported, getItem, setItem, removeItem, SESSION_ID_BYTES } from "../utils";
2
- import { debug, generateUniqueId, now } from "../utils";
1
+ import { isSupported, getItem, setItem, removeItem } from "../utils";
2
+ import { debug, now } from "../utils";
3
3
  import { info, warn } from "../utils";
4
+ import { generateSessionId } from "../utils/session-id";
4
5
  const SESSION_STORAGE_KEY = "d0_session";
5
6
  const STORAGE_SEPARATOR_KEY = "#";
6
7
  const DEFAULT_SESSION_INACTIVITY_TIMEOUT_MILLIS = 1000 * 60 * 60 * 3;
@@ -10,6 +11,8 @@ export let sessionId = null;
10
11
  export function trackSessions(sessionInactivityTimeoutMillis, sessionTerminationTimeoutMillis) {
11
12
  if (!isSupported) {
12
13
  debug("Storage API is not available and session tracking is therefore not supported.");
14
+ // we still generate a session ID to ensure that the session ID is always available
15
+ sessionId = generateSessionId();
13
16
  return;
14
17
  }
15
18
  if (!sessionInactivityTimeoutMillis) {
@@ -31,7 +34,7 @@ export function trackSessions(sessionInactivityTimeoutMillis, sessionTermination
31
34
  }
32
35
  else {
33
36
  session = {
34
- id: generateUniqueId(SESSION_ID_BYTES),
37
+ id: generateSessionId(),
35
38
  startTime: now(),
36
39
  lastActivityTime: now(),
37
40
  };
@@ -43,8 +46,10 @@ export function trackSessions(sessionInactivityTimeoutMillis, sessionTermination
43
46
  warn("Failed to record session information", e);
44
47
  }
45
48
  }
49
+ /**
50
+ * The session will be terminated. This only takes effect on the next physical page load.
51
+ */
46
52
  export function terminateSession() {
47
- sessionId = null;
48
53
  if (!isSupported) {
49
54
  return;
50
55
  }
@@ -5,9 +5,9 @@ import { sendLog } from "../transport";
5
5
  import { addAttribute } from "../utils/otel";
6
6
  import { addCommonAttributes } from "../attributes";
7
7
  export function startWebVitalsInstrumentation() {
8
- onLCP(onWebVital);
9
- onINP(onWebVital);
10
- onCLS(onWebVital);
8
+ onLCP(onWebVital, { reportAllChanges: true });
9
+ onINP(onWebVital, { reportAllChanges: true });
10
+ onCLS(onWebVital, { reportAllChanges: true });
11
11
  }
12
12
  function onWebVital(metric) {
13
13
  const attributes = [];
@@ -1,7 +1,7 @@
1
1
  export const SPAN_ID_BYTES = 8;
2
2
  export const TRACE_ID_BYTES = 16;
3
3
  export const PAGE_LOAD_ID_BYTES = TRACE_ID_BYTES;
4
- export const SESSION_ID_BYTES = TRACE_ID_BYTES;
4
+ export const SESSION_ID_BYTES = SPAN_ID_BYTES;
5
5
  export const TAB_ID_BYTES = SPAN_ID_BYTES;
6
6
  const SHARED_CHAR_CODES_ARRAY = Array(32);
7
7
  export function generateUniqueId(byteCount) {
@@ -10,7 +10,7 @@ function generateId(length) {
10
10
  }
11
11
  return generateUniqueId(Math.ceil(length / 2)).substring(0, length);
12
12
  }
13
- describe("generateId", () => {
13
+ describe("generateUniqueId", () => {
14
14
  it("returns a string of the expected length", () => {
15
15
  const id = generateId(10);
16
16
  expect(id).toHaveLength(10);
@@ -1,9 +1,11 @@
1
1
  import { nowNanos } from "../time";
2
- import { generateUniqueId, SPAN_ID_BYTES, TRACE_ID_BYTES } from "../id";
2
+ import { generateUniqueId, SPAN_ID_BYTES } from "../id";
3
3
  import { SPAN_KIND_CLIENT, SPAN_STATUS_UNSET } from "../../semantic-conventions";
4
+ import { sessionId } from "../../api/session";
5
+ import { generateTraceId } from "../trace-id";
4
6
  export function startSpan(name) {
5
7
  return {
6
- traceId: generateUniqueId(TRACE_ID_BYTES),
8
+ traceId: generateTraceId(sessionId),
7
9
  spanId: generateUniqueId(SPAN_ID_BYTES),
8
10
  name,
9
11
  // Always CLIENT for now https://github.com/open-telemetry/opentelemetry-proto/blob/ac3242b03157295e4ee9e616af53b81517b06559/opentelemetry/proto/trace/v1/trace.proto#L143-L169
@@ -51,10 +51,12 @@ export function addTraceContextHttpHeaders(fn, ctx, span) {
51
51
  * Random Trace ID: 00000010 - IF set the component guarantees that the seven right most bytes of the trace-id
52
52
  * are randomly generated. Downstream processors are then able to rely on this for technical things like shard keys.
53
53
  *
54
+ * NOTE: For now we just send the "sampled" flag because not all components support the random trace ID flag.
55
+ *
54
56
  * References:
55
57
  * https://www.w3.org/TR/trace-context-2/#traceparent-header
56
58
  * https://www.w3.org/TR/trace-context-2/#trace-flags
57
59
  * https://www.w3.org/TR/trace-context-2/#random-trace-id-flag
58
60
  */
59
- fn.call(ctx, "traceparent", `00-${span.traceId}-${span.spanId}-03`);
61
+ fn.call(ctx, "traceparent", `00-${span.traceId}-${span.spanId}-01`);
60
62
  }
@@ -0,0 +1,18 @@
1
+ import * as localStorage from "./local-storage";
2
+ import { generateUniqueId, SESSION_ID_BYTES } from "./id";
3
+ const SESSION_FLAGS = {
4
+ EPHEMERAL_SESSION: 0x01, // 00000001 - if the browser does NOT support local storage we count the session as ephemeral because we cannot persist it
5
+ };
6
+ function encodeSessionFlags(flags) {
7
+ let byte = 0;
8
+ if (flags.ephemeralSession) {
9
+ byte |= SESSION_FLAGS.EPHEMERAL_SESSION;
10
+ }
11
+ return byte.toString(16).padStart(2, "0");
12
+ }
13
+ export function generateSessionId() {
14
+ const sessionFlags = {
15
+ ephemeralSession: !localStorage.isSupported,
16
+ };
17
+ return `${encodeSessionFlags(sessionFlags)}${generateUniqueId(SESSION_ID_BYTES - 1)}`;
18
+ }
@@ -0,0 +1,26 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { generateSessionId } from "./session-id";
3
+ import * as localStorage from "./local-storage";
4
+ describe("generateSessionId", () => {
5
+ it("returns a session ID of the expected length", () => {
6
+ const sessionId = generateSessionId();
7
+ expect(sessionId).toHaveLength(16);
8
+ });
9
+ it("returns a session ID with proper flags when storage is supported", () => {
10
+ vi.spyOn(localStorage, "isSupported", "get").mockReturnValue(true);
11
+ const sessionId = generateSessionId();
12
+ console.log(sessionId);
13
+ expect(sessionId.startsWith("00")).toBe(true);
14
+ });
15
+ it("returns a session ID with proper flags when storage is NOT supported", () => {
16
+ vi.spyOn(localStorage, "isSupported", "get").mockReturnValue(false);
17
+ const sessionId = generateSessionId();
18
+ console.log(sessionId);
19
+ expect(sessionId.startsWith("01")).toBe(true);
20
+ });
21
+ it("returns a unique session ID on each call", () => {
22
+ const sessionId1 = generateSessionId();
23
+ const sessionId2 = generateSessionId();
24
+ expect(sessionId1).not.toBe(sessionId2);
25
+ });
26
+ });
@@ -0,0 +1,18 @@
1
+ import { generateUniqueId, SESSION_ID_BYTES, TRACE_ID_BYTES } from "./id";
2
+ const TRACE_ID_PREFIX = "d042";
3
+ const TRACE_FLAGS = {
4
+ WITHOUT_SESSION: 0x01, // 00000001 - in some edge cases we do not have a session ID, so we need to mark the trace as such
5
+ };
6
+ function encodeTraceFlags(flags) {
7
+ let byte = 0;
8
+ if (flags.withoutSession) {
9
+ byte |= TRACE_FLAGS.WITHOUT_SESSION;
10
+ }
11
+ return byte.toString(16).padStart(2, "0");
12
+ }
13
+ export function generateTraceId(sessionId) {
14
+ if (!sessionId) {
15
+ return `${TRACE_ID_PREFIX}${encodeTraceFlags({ withoutSession: true })}${generateUniqueId(TRACE_ID_BYTES - 3)}`;
16
+ }
17
+ return `${TRACE_ID_PREFIX}${encodeTraceFlags({ withoutSession: false })}${sessionId}${generateUniqueId(TRACE_ID_BYTES - SESSION_ID_BYTES - 3)}`;
18
+ }
@@ -0,0 +1,32 @@
1
+ import { expect, describe, it } from "vitest";
2
+ import { generateTraceId } from "./trace-id";
3
+ describe("generateTraceId", () => {
4
+ it("returns a trace ID of the expected length", () => {
5
+ const traceId = generateTraceId(null);
6
+ expect(traceId).toHaveLength(32);
7
+ });
8
+ it("returns a trace ID with correct prefix when a session id is set", () => {
9
+ const traceId = generateTraceId("abcdef1234567890");
10
+ expect(traceId.substring(0, 6)).toEqual("d04200");
11
+ });
12
+ it("returns a trace ID with correct prefix when NOT session is set", () => {
13
+ const traceId = generateTraceId(null);
14
+ expect(traceId.substring(0, 6)).toEqual("d04201");
15
+ });
16
+ it("returns a unique trace ID on each call", () => {
17
+ const sessiondId = "abcdef1234567890";
18
+ const traceId1 = generateTraceId("abcdef1234567890");
19
+ const traceId2 = generateTraceId("abcdef1234567890");
20
+ expect(traceId1).not.toBe(traceId2);
21
+ expect(traceId1.substring(6, 22)).toBe(sessiondId);
22
+ expect(traceId2.substring(6, 22)).toBe(sessiondId);
23
+ });
24
+ it("returns a unique trace ID when session id is not set", () => {
25
+ const traceId1 = generateTraceId(null);
26
+ const sessionId1 = traceId1.substring(6, 22);
27
+ const traceId2 = generateTraceId(null);
28
+ const sessionId2 = traceId2.substring(6, 22);
29
+ expect(traceId1).not.toBe(traceId2);
30
+ expect(sessionId1).not.toBe(sessionId2);
31
+ });
32
+ });