@dash0/sdk-web 0.15.0 → 0.16.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.
Files changed (54) hide show
  1. package/README.md +35 -515
  2. package/dist/dash0.iife.js +1 -1
  3. package/dist/dash0.iife.js.map +1 -1
  4. package/dist/dash0.js +1 -1
  5. package/dist/dash0.js.map +1 -1
  6. package/dist/dash0.umd.cjs +1 -1
  7. package/dist/dash0.umd.cjs.map +1 -1
  8. package/dist/modules/api/init.js +30 -0
  9. package/dist/modules/api/init_test.js +102 -6
  10. package/dist/modules/api/report-error.js +1 -1
  11. package/dist/modules/instrumentations/errors/event-handlers.js +2 -2
  12. package/dist/modules/instrumentations/errors/unhandled-error.js +1 -1
  13. package/dist/modules/instrumentations/errors/unhandled-promise-rejection.js +4 -4
  14. package/dist/modules/instrumentations/http/fetch.js +125 -28
  15. package/dist/modules/instrumentations/http/fetch_test.js +170 -0
  16. package/dist/modules/instrumentations/http/propagator-integration_test.js +77 -0
  17. package/dist/modules/transport/index.js +2 -0
  18. package/dist/modules/utils/index.js +1 -0
  19. package/dist/modules/utils/otel/span.js +9 -0
  20. package/dist/modules/utils/otel/trace-context.js +24 -1
  21. package/dist/modules/utils/otel/trace-context_test.js +40 -0
  22. package/dist/modules/utils/performance.js +16 -3
  23. package/dist/tsconfig.tsbuildinfo +1 -1
  24. package/dist/types/entrypoint/npm-package.d.ts +1 -1
  25. package/dist/types/instrumentations/errors/unhandled-error.d.ts +1 -1
  26. package/dist/types/instrumentations/http/fetch_test.d.ts +1 -0
  27. package/dist/types/instrumentations/http/propagator-integration_test.d.ts +1 -0
  28. package/dist/types/transport/index.d.ts +1 -1
  29. package/dist/types/types/options.d.ts +6 -1
  30. package/dist/types/utils/index.d.ts +1 -0
  31. package/dist/types/utils/otel/span.d.ts +5 -1
  32. package/dist/types/utils/otel/trace-context.d.ts +2 -1
  33. package/dist/types/utils/otel/trace-context_test.d.ts +1 -0
  34. package/dist/types/utils/performance.d.ts +5 -1
  35. package/dist/types/vars.d.ts +11 -0
  36. package/package.json +1 -1
  37. package/src/api/init.ts +34 -0
  38. package/src/api/init_test.ts +130 -9
  39. package/src/api/report-error.ts +1 -1
  40. package/src/entrypoint/npm-package.ts +1 -1
  41. package/src/instrumentations/errors/event-handlers.ts +2 -2
  42. package/src/instrumentations/errors/unhandled-error.ts +1 -1
  43. package/src/instrumentations/errors/unhandled-promise-rejection.ts +4 -4
  44. package/src/instrumentations/http/fetch.ts +148 -30
  45. package/src/instrumentations/http/fetch_test.ts +191 -0
  46. package/src/instrumentations/http/propagator-integration_test.ts +93 -0
  47. package/src/transport/index.ts +3 -1
  48. package/src/types/options.ts +7 -1
  49. package/src/utils/index.ts +1 -0
  50. package/src/utils/otel/span.ts +16 -1
  51. package/src/utils/otel/trace-context.ts +30 -1
  52. package/src/utils/otel/trace-context_test.ts +59 -0
  53. package/src/utils/performance.ts +21 -4
  54. package/src/vars.ts +14 -0
package/src/api/init.ts CHANGED
@@ -73,6 +73,8 @@ export function init(opts: InitOptions) {
73
73
  )
74
74
  );
75
75
 
76
+ initializePropagators(opts);
77
+
76
78
  initializeResourceAttributes(opts);
77
79
  initializeSignalAttributes(opts);
78
80
  initializeTabId();
@@ -185,6 +187,38 @@ function detectDeploymentId(opts: InitOptions): string | undefined {
185
187
  }
186
188
  }
187
189
 
190
+ function initializePropagators(opts: InitOptions) {
191
+ if (opts.propagators) {
192
+ if (opts.propagateTraceHeadersCorsURLs) {
193
+ warn(
194
+ "Both 'propagators' and deprecated 'propagateTraceHeadersCorsURLs' were provided. Using 'propagators' configuration. Please migrate to the new 'propagators' config."
195
+ );
196
+ }
197
+ vars.propagators = opts.propagators;
198
+ }
199
+ // Handle legacy configuration
200
+ else if (opts.propagateTraceHeadersCorsURLs && opts.propagateTraceHeadersCorsURLs.length > 0) {
201
+ warn("'propagateTraceHeadersCorsURLs' is deprecated. Please use the new 'propagators' configuration.");
202
+ // Convert legacy config to new format - only include cross-origin URLs since same-origin is automatic
203
+ vars.propagators = [
204
+ {
205
+ type: "traceparent",
206
+ match: [...opts.propagateTraceHeadersCorsURLs],
207
+ },
208
+ ];
209
+ }
210
+ // Default configuration - traceparent with empty match array
211
+ // Same-origin requests get ALL configured propagators, so this ensures traceparent for same-origin
212
+ else {
213
+ vars.propagators = [
214
+ {
215
+ type: "traceparent",
216
+ match: [],
217
+ },
218
+ ];
219
+ }
220
+ }
221
+
188
222
  function isInstrumentationEnabled(name: InstrumentationName, opts: InitOptions): boolean {
189
223
  const instrumentations = opts.enabledInstrumentations;
190
224
 
@@ -1,5 +1,7 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
2
  import { InitOptions, InstrumentationName } from "../types/options";
3
+ import { PropagatorConfig, Vars } from "../vars";
4
+ import { init as initFun } from "./init";
3
5
 
4
6
  // Mock all the instrumentation modules
5
7
  vi.mock("../instrumentations/web-vitals", () => ({
@@ -18,21 +20,30 @@ vi.mock("../instrumentations/navigation", () => ({
18
20
  startNavigationInstrumentation: vi.fn(),
19
21
  }));
20
22
 
21
- import { startWebVitalsInstrumentation } from "../instrumentations/web-vitals";
22
23
  import { startErrorInstrumentation } from "../instrumentations/errors";
23
24
  import { instrumentFetch } from "../instrumentations/http/fetch";
24
25
  import { startNavigationInstrumentation } from "../instrumentations/navigation";
26
+ import { startWebVitalsInstrumentation } from "../instrumentations/web-vitals";
25
27
 
26
28
  describe("init", () => {
27
29
  const baseOptions: InitOptions = {
28
30
  serviceName: "test-service",
29
31
  endpoint: { url: "https://test-endpoint.com", authToken: "invalid" },
30
32
  };
33
+ let vars: Vars;
34
+ let init: typeof initFun;
31
35
 
32
- beforeEach(() => {
36
+ beforeEach(async () => {
33
37
  vi.clearAllMocks();
34
38
  // Reset the hasBeenInitialised flag by re-importing the module
35
39
  vi.resetModules();
40
+ // Reset vars state after module reset
41
+ // since init itself needs vars, this needs to imported again as well.
42
+ // Otherwise the tests that make assumptions on the vars fail because they use a different reference.
43
+ const { vars: importedVars } = await import("../vars");
44
+ const { init: importedInit } = await import("./init");
45
+ vars = importedVars;
46
+ init = importedInit;
36
47
  });
37
48
 
38
49
  afterEach(() => {
@@ -41,8 +52,6 @@ describe("init", () => {
41
52
 
42
53
  describe("instrumentation enablement", () => {
43
54
  it("should enable all instrumentations when enabledInstrumentations is undefined", async () => {
44
- const { init } = await import("./init");
45
-
46
55
  init({
47
56
  ...baseOptions,
48
57
  enabledInstrumentations: undefined,
@@ -69,8 +78,6 @@ describe("init", () => {
69
78
 
70
79
  instrumentations.forEach((instrumentation) => {
71
80
  it(`should enable ${instrumentation} instrumentation when present in enabledInstrumentations array`, async () => {
72
- const { init } = await import("./init");
73
-
74
81
  init({
75
82
  ...baseOptions,
76
83
  enabledInstrumentations: [instrumentation],
@@ -87,8 +94,6 @@ describe("init", () => {
87
94
  });
88
95
 
89
96
  it(`should not enable ${instrumentation} instrumentation when not present in enabledInstrumentations array`, async () => {
90
- const { init } = await import("./init");
91
-
92
97
  const otherInstrumentations = instrumentations.filter((i) => i !== instrumentation);
93
98
 
94
99
  init({
@@ -105,4 +110,120 @@ describe("init", () => {
105
110
  });
106
111
  });
107
112
  });
113
+
114
+ describe("propagator configuration", () => {
115
+ it("should set propagators configuration when provided", async () => {
116
+ const propagators: PropagatorConfig[] = [
117
+ { type: "traceparent" as const, match: [/.*\/api\/.*/] },
118
+ { type: "xray" as const, match: [/.*\.amazonaws\.com.*/] },
119
+ ];
120
+
121
+ init({
122
+ ...baseOptions,
123
+ propagators,
124
+ });
125
+
126
+ expect(vars.propagators).toEqual(propagators);
127
+ });
128
+
129
+ it("should warn when both propagators and legacy config are provided", async () => {
130
+ const spyOnWarn = vi.spyOn(console, "warn");
131
+ const propagators: PropagatorConfig[] = [{ type: "traceparent" as const, match: [/.*\/api\/.*/] }];
132
+
133
+ init({
134
+ ...baseOptions,
135
+ propagators,
136
+ propagateTraceHeadersCorsURLs: [/.*\.example\.com.*/],
137
+ });
138
+
139
+ expect(spyOnWarn).toHaveBeenCalledWith(
140
+ "Both 'propagators' and deprecated 'propagateTraceHeadersCorsURLs' were provided. Using 'propagators' configuration. Please migrate to the new 'propagators' config."
141
+ );
142
+ expect(vars.propagators).toEqual(propagators);
143
+ });
144
+
145
+ it("should convert legacy config to new format with deprecation warning", async () => {
146
+ const spyOnWarn = vi.spyOn(console, "warn");
147
+ const legacyConfig = [/.*\.example\.com.*/, /.*\.test\.com.*/];
148
+
149
+ init({
150
+ ...baseOptions,
151
+ propagateTraceHeadersCorsURLs: legacyConfig,
152
+ });
153
+
154
+ expect(spyOnWarn).toHaveBeenCalledWith(
155
+ "'propagateTraceHeadersCorsURLs' is deprecated. Please use the new 'propagators' configuration."
156
+ );
157
+
158
+ expect(vars.propagators).toEqual([
159
+ {
160
+ type: "traceparent",
161
+ match: [...legacyConfig],
162
+ },
163
+ ]);
164
+ });
165
+
166
+ it("should set default propagators when no configuration is provided", async () => {
167
+ init(baseOptions);
168
+
169
+ expect(vars.propagators).toEqual([
170
+ {
171
+ type: "traceparent",
172
+ match: [],
173
+ },
174
+ ]);
175
+ });
176
+
177
+ it("should not convert empty legacy config", async () => {
178
+ const spyOnWarn = vi.spyOn(console, "warn");
179
+
180
+ init({
181
+ ...baseOptions,
182
+ propagateTraceHeadersCorsURLs: [],
183
+ });
184
+
185
+ expect(spyOnWarn).not.toHaveBeenCalled();
186
+ expect(vars.propagators).toEqual([
187
+ {
188
+ type: "traceparent",
189
+ match: [],
190
+ },
191
+ ]);
192
+ });
193
+
194
+ it("should handle mixed pattern types in propagators", async () => {
195
+ const spyOnWarn = vi.spyOn(console, "warn");
196
+
197
+ const propagators: PropagatorConfig[] = [
198
+ {
199
+ type: "traceparent" as const,
200
+ match: [/.*\/api\/.*/],
201
+ },
202
+ {
203
+ type: "xray" as const,
204
+ match: [/.*\.amazonaws\.com.*/, /.*\.aws\.com.*/],
205
+ },
206
+ ];
207
+
208
+ init({
209
+ ...baseOptions,
210
+ propagators,
211
+ });
212
+
213
+ expect(vars.propagators).toEqual(propagators);
214
+ expect(spyOnWarn).not.toHaveBeenCalled();
215
+ });
216
+
217
+ it("should handle empty propagators array", async () => {
218
+ const spyOnWarn = vi.spyOn(console, "warn");
219
+
220
+ init({
221
+ ...baseOptions,
222
+ propagators: [],
223
+ });
224
+
225
+ expect(vars.propagators).toEqual([]);
226
+ expect(spyOnWarn).not.toHaveBeenCalled();
227
+ });
228
+ });
108
229
  });
@@ -1,4 +1,4 @@
1
- import { reportError as reportErrorInternal } from "../instrumentations/errors/unhandled-error";
1
+ import { reportUnhandledError as reportErrorInternal } from "../instrumentations/errors/unhandled-error";
2
2
  import { ReportErrorOpts, ErrorLike } from "../types/errors";
3
3
 
4
4
  export function reportError(error: string | ErrorLike, opts?: ReportErrorOpts) {
@@ -13,7 +13,7 @@ export { reportError } from "../api/report-error";
13
13
  // Additional utility types
14
14
  export type { AttributeValueType } from "../utils/otel";
15
15
  export type { AnyValue } from "../types/otlp";
16
- export type { PageViewMeta } from "../vars";
16
+ export type { PageViewMeta, PropagatorConfig, PropagatorType } from "../vars";
17
17
  export type { UrlAttributeScrubber, UrlAttributeRecord } from "../attributes/url";
18
18
 
19
19
  export function init(opts: InitOptions): void {
@@ -5,7 +5,7 @@ import {
5
5
  EventListenerOptionsOrUseCapture,
6
6
  popWrappedDomEventListener,
7
7
  } from "./async-function-wrapping";
8
- import { ignoreNextOnErrorEvent } from "./unhandled-error";
8
+ import { ignoreNextOnErrorEvent, reportUnhandledError } from "./unhandled-error";
9
9
 
10
10
  export function startEventHandlerInstrumentation() {
11
11
  if (vars.wrapEventHandlers) {
@@ -44,7 +44,7 @@ function wrapEventTarget(EventTarget: WindowType["EventTarget"] | undefined) {
44
44
  try {
45
45
  return fn.apply(this, arguments as any);
46
46
  } catch (e) {
47
- reportError(e as any);
47
+ reportUnhandledError(e as any);
48
48
  ignoreNextOnErrorEvent();
49
49
  throw e;
50
50
  }
@@ -75,7 +75,7 @@ export function startOnErrorInstrumentation() {
75
75
  };
76
76
  }
77
77
 
78
- export function reportError(error: string | ErrorLike, opts?: ReportErrorOpts) {
78
+ export function reportUnhandledError(error: string | ErrorLike, opts?: ReportErrorOpts) {
79
79
  if (!error) {
80
80
  return;
81
81
  }
@@ -1,5 +1,5 @@
1
1
  import { win } from "../../utils";
2
- import { reportError } from "./unhandled-error";
2
+ import { reportUnhandledError } from "./unhandled-error";
3
3
 
4
4
  const MESSAGE_PREFIX = "Unhandled promise rejection: ";
5
5
  const STACK_UNAVAILABLE_MESSAGE = "<unavailable because Promise wasn't rejected with an Error object>";
@@ -12,17 +12,17 @@ export function startUnhandledRejectionInstrumentation() {
12
12
 
13
13
  export function onUnhandledRejection(event: PromiseRejectionEvent) {
14
14
  if (event.reason == null) {
15
- reportError({
15
+ reportUnhandledError({
16
16
  message: MESSAGE_PREFIX + "<no reason defined>",
17
17
  stack: STACK_UNAVAILABLE_MESSAGE,
18
18
  });
19
19
  } else if (typeof event.reason.message === "string") {
20
- reportError({
20
+ reportUnhandledError({
21
21
  message: MESSAGE_PREFIX + event.reason.message,
22
22
  stack: typeof event.reason.stack === "string" ? event.reason.stack : STACK_UNAVAILABLE_MESSAGE,
23
23
  });
24
24
  } else if (typeof event.reason !== "object") {
25
- reportError({
25
+ reportUnhandledError({
26
26
  message: MESSAGE_PREFIX + event.reason,
27
27
  stack: STACK_UNAVAILABLE_MESSAGE,
28
28
  });
@@ -1,9 +1,10 @@
1
- import { debug, observeResourcePerformance, win } from "../../utils";
1
+ import { debug, observeResourcePerformance, perf, win, setTimeout, isSameOrigin, wrap, parseUrl } from "../../utils";
2
2
  import { isUrlIgnored, matchesAny } from "../../utils/ignore-rules";
3
3
  import {
4
4
  addAttribute,
5
5
  setSpanStatus,
6
- addTraceContextHttpHeaders,
6
+ addW3CTraceContextHttpHeaders,
7
+ addXRayTraceContextHttpHeaders,
7
8
  endSpan,
8
9
  errorToSpanStatus,
9
10
  Exception,
@@ -19,8 +20,7 @@ import {
19
20
  SPAN_STATUS_ERROR,
20
21
  SPAN_STATUS_UNSET,
21
22
  } from "../../semantic-conventions";
22
- import { isSameOrigin, wrap, parseUrl } from "../../utils";
23
- import { vars } from "../../vars";
23
+ import { vars, PropagatorType } from "../../vars";
24
24
  import { httpRequestHeaderKey, httpResponseHeaderKey } from "../../utils/otel/http";
25
25
  import { sendSpan } from "../../transport";
26
26
  import { addResourceNetworkEvents, addResourceSize, HTTP_METHOD_OTHER, isWellKnownHttpMethod } from "./utils";
@@ -72,20 +72,21 @@ function wrapFetch(original: typeof fetch) {
72
72
  addAttribute(span.attributes, HTTP_REQUEST_METHOD_ORIGINAL, originalMethod);
73
73
  }
74
74
 
75
- const shouldSetCorrelationHeaders = isSameOrigin(url) || matchesAny(vars.propagateTraceHeadersCorsURLs, url);
75
+ const propagatorTypes = determinePropagatorTypes(url);
76
+ const shouldSetCorrelationHeaders = propagatorTypes.length > 0;
76
77
  if (shouldSetCorrelationHeaders) {
77
78
  if (copyOfInit?.headers) {
78
79
  // ensure we have a unified container for the headers
79
80
  copyOfInit.headers = new Headers(copyOfInit.headers);
80
- addTraceContextHttpHeaders(copyOfInit.headers.append, copyOfInit.headers, span);
81
+ addTraceContextHttpHeaders(copyOfInit.headers.append, copyOfInit.headers, span, propagatorTypes);
81
82
  } else if (input instanceof Request) {
82
- addTraceContextHttpHeaders(request.headers.append, request.headers, span);
83
+ addTraceContextHttpHeaders(request.headers.append, request.headers, span, propagatorTypes);
83
84
  } else {
84
85
  if (!copyOfInit) {
85
86
  copyOfInit = {};
86
87
  }
87
88
  copyOfInit.headers = new Headers();
88
- addTraceContextHttpHeaders(copyOfInit.headers.append, copyOfInit.headers, span);
89
+ addTraceContextHttpHeaders(copyOfInit.headers.append, copyOfInit.headers, span, propagatorTypes);
89
90
  }
90
91
  }
91
92
 
@@ -109,18 +110,18 @@ function wrapFetch(original: typeof fetch) {
109
110
 
110
111
  performanceObserver.start();
111
112
  try {
112
- const response = await original(input instanceof Request ? request : input, copyOfInit);
113
- addResponseData(span, response);
113
+ const origResponse = await original(input instanceof Request ? request : input, copyOfInit);
114
+ addResponseData(span, origResponse);
114
115
 
115
- // We use a separate promise here because this needs to happen in parallel to application code consuming the response
116
- waitForFullResponse(response)
117
- .then(() => performanceObserver.end())
118
- .catch((e) => {
116
+ return wrapResponse(
117
+ origResponse,
118
+ vars.maxToleranceForResourceTimingsMillis,
119
+ () => performanceObserver.end(),
120
+ (e) => {
119
121
  performanceObserver.cancel();
120
- endSpanOnError(span, e as Exception);
121
- });
122
-
123
- return response;
122
+ endSpanOnError(span, e);
123
+ }
124
+ );
124
125
  } catch (e) {
125
126
  performanceObserver.cancel();
126
127
  endSpanOnError(span, e as Exception);
@@ -175,20 +176,86 @@ function addResponseData(span: InProgressSpan, response: Response) {
175
176
  tryCaptureHttpHeaders(response.headers, span, (k) => httpResponseHeaderKey(k));
176
177
  }
177
178
 
178
- function waitForFullResponse(response: Response): Promise<void> {
179
- return new Promise((resolve) => {
180
- const clonedResponse = response.clone();
181
- const body = clonedResponse.body;
179
+ /**
180
+ * Wraps the response to be able to detect when it is fully read
181
+ * @param originalResponse
182
+ * @param readTimeoutMs Timeout applied between reading of response chunks, if exceeded the response is considered abandoned and onDone is called.
183
+ * @param onDone Called when the response is completely read or with "fallbackEndTs" when reading timed out.
184
+ * @param onError
185
+ */
186
+ function wrapResponse(
187
+ originalResponse: Response,
188
+ readTimeoutMs: number,
189
+ onDone: (fallbackEndTs?: number) => void,
190
+ onError: (e: Exception) => void
191
+ ): Response {
192
+ // When the response was wrapped (i.e. first available to js) we use this to replace or find the actual end timestamp
193
+ // in case the response body is never read by js
194
+ let fallbackTs: number = perf.now();
195
+ const body = originalResponse.body;
196
+
197
+ if (!body) {
198
+ onDone();
199
+ return originalResponse;
200
+ }
201
+
202
+ let cbCalled: boolean = false;
203
+ const handleDone = (fallbackEndTs?: number) => {
204
+ if (cbCalled) return;
205
+ onDone(fallbackEndTs);
206
+ cbCalled = true;
207
+ };
208
+ const handleError = (e: Exception) => {
209
+ if (cbCalled) return;
210
+ onError(e);
211
+ cbCalled = true;
212
+ };
213
+
214
+ let bodyNeverCompletelyReadTimeout = setTimeout(() => handleDone(fallbackTs), readTimeoutMs);
182
215
 
183
- if (!body) return resolve();
216
+ const reader = body.getReader();
217
+ const stream = new ReadableStream({
218
+ async pull(controller) {
219
+ try {
220
+ clearTimeout(bodyNeverCompletelyReadTimeout);
221
+ const { value, done } = await reader.read();
222
+ if (done) {
223
+ reader.releaseLock();
224
+ controller.close();
225
+ handleDone();
226
+ } else {
227
+ fallbackTs = perf.now();
228
+ bodyNeverCompletelyReadTimeout = setTimeout(() => handleDone(fallbackTs), readTimeoutMs);
229
+ controller.enqueue(value);
230
+ }
231
+ } catch (e) {
232
+ handleError(e as Exception);
233
+ controller.error(e);
234
+
235
+ try {
236
+ reader.releaseLock();
237
+ } catch {
238
+ // Spec reference:
239
+ // https://streams.spec.whatwg.org/#default-reader-release-lock
240
+ //
241
+ // releaseLock() only throws if called on an invalid reader
242
+ // (i.e. reader.[[stream]] is undefined, meaning the lock is already released
243
+ // or the reader was never associated). In normal use this cannot happen.
244
+ // This catch is defensive only.
245
+ }
246
+ }
247
+ },
248
+ cancel(reason) {
249
+ clearTimeout(bodyNeverCompletelyReadTimeout);
250
+ handleDone();
251
+ return reader.cancel(reason);
252
+ },
253
+ });
184
254
 
185
- const reader = body.getReader();
186
- const read = async () => {
187
- const { done } = await reader.read();
188
- if (done) return resolve();
189
- return read();
190
- };
191
- return read();
255
+ return new Response(stream, {
256
+ status: originalResponse.status,
257
+ statusText: originalResponse.statusText,
258
+ headers: originalResponse.headers,
192
259
  });
193
260
  }
194
261
 
@@ -196,3 +263,54 @@ function endSpanOnError(span: InProgressSpan, error: Exception) {
196
263
  recordException(span, error);
197
264
  sendSpan(endSpan(span, errorToSpanStatus(error), undefined));
198
265
  }
266
+
267
+ function determinePropagatorTypes(url: string): PropagatorType[] {
268
+ const matchingTypes: PropagatorType[] = [];
269
+ const isUrlSameOrigin = isSameOrigin(url);
270
+
271
+ // For same-origin requests, always include traceparent + all configured propagators
272
+ if (isUrlSameOrigin) {
273
+ // Always add traceparent for same-origin requests
274
+ matchingTypes.push("traceparent");
275
+
276
+ // Add all other configured propagator types for same-origin requests
277
+ if (vars.propagators) {
278
+ for (const propagator of vars.propagators) {
279
+ if (propagator.type !== "traceparent" && !matchingTypes.includes(propagator.type)) {
280
+ matchingTypes.push(propagator.type);
281
+ }
282
+ }
283
+ }
284
+ return matchingTypes;
285
+ }
286
+
287
+ // For cross-origin requests, use new propagators config if available
288
+ if (vars.propagators) {
289
+ for (const propagator of vars.propagators) {
290
+ if (matchesAny(propagator.match, url)) {
291
+ // Avoid duplicates
292
+ if (!matchingTypes.includes(propagator.type)) {
293
+ matchingTypes.push(propagator.type);
294
+ }
295
+ }
296
+ }
297
+ return matchingTypes;
298
+ }
299
+
300
+ return [];
301
+ }
302
+
303
+ function addTraceContextHttpHeaders(
304
+ fn: (name: string, value: string) => void,
305
+ ctx: unknown,
306
+ span: InProgressSpan,
307
+ types: PropagatorType[]
308
+ ) {
309
+ for (const type of types) {
310
+ if (type === "xray") {
311
+ addXRayTraceContextHttpHeaders(fn, ctx, span);
312
+ } else {
313
+ addW3CTraceContextHttpHeaders(fn, ctx, span);
314
+ }
315
+ }
316
+ }