@grafana/faro-react-native-tracing 1.0.0 → 1.2.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.
package/README.md CHANGED
@@ -41,11 +41,63 @@ const faro = initializeFaro({
41
41
 
42
42
  That's it! HTTP requests via `fetch()` are now automatically traced (and correlated with user actions when user action tracking is enabled) and sent to your Faro collector.
43
43
 
44
+ ## Fetch vs XHR in React Native
45
+
46
+ By default, this package traces `fetch()` requests only.
47
+
48
+ React Native implements `fetch()` on top of `XMLHttpRequest`. If both fetch and XHR instrumentation are enabled for the same URL, one logical `fetch()` request can produce two spans: one from `FetchInstrumentation` and one from the underlying XHR layer. To avoid duplicate HTTP telemetry, XHR tracing is disabled by default.
49
+
50
+ If your app uses axios or direct `XMLHttpRequest` calls instead of `fetch()`, enable XHR tracing explicitly:
51
+
52
+ ```typescript
53
+ initializeFaro({
54
+ enableTracing: true,
55
+ tracingOptions: {
56
+ instrumentationOptions: {
57
+ enableXhrInstrumentation: true,
58
+ },
59
+ },
60
+ });
61
+ ```
62
+
63
+ If your app uses both `fetch()` and axios/XHR, avoid tracing the same request twice. Either trace one API surface only:
64
+
65
+ ```typescript
66
+ initializeFaro({
67
+ enableTracing: true,
68
+ tracingOptions: {
69
+ instrumentationOptions: {
70
+ // Useful for axios/direct XHR apps.
71
+ enableFetchInstrumentation: false,
72
+ enableXhrInstrumentation: true,
73
+ },
74
+ },
75
+ });
76
+ ```
77
+
78
+ Or enable both and use `ignoreUrls` on one instrumentation so URL patterns do not overlap:
79
+
80
+ ```typescript
81
+ initializeFaro({
82
+ enableTracing: true,
83
+ tracingOptions: {
84
+ instrumentationOptions: {
85
+ enableXhrInstrumentation: true,
86
+ xhrInstrumentationOptions: {
87
+ // Example: fetch() handles your API calls, so ignore those URLs in XHR.
88
+ ignoreUrls: [/\/api\//],
89
+ },
90
+ },
91
+ },
92
+ });
93
+ ```
94
+
44
95
  ## Features
45
96
 
46
97
  ### 🚀 **Automatic Tracing**
47
98
 
48
- - **Fetch Instrumentation**: HTTP requests are automatically traced with no code changes
99
+ - **Fetch Instrumentation**: `fetch()` requests are automatically traced with no code changes
100
+ - **Optional XHR Instrumentation**: Enable for axios or direct `XMLHttpRequest` calls
49
101
  - **Session Correlation**: Traces are correlated with Faro sessions for complete user journey tracking
50
102
  - **User Action Correlation**: Traces are correlated with Faro user actions (when user action tracking is enabled)
51
103
  - **User Context**: User information is automatically added to span attributes
@@ -114,6 +166,10 @@ new TracingInstrumentation({
114
166
  // URLs that should receive trace context headers
115
167
  propagateTraceHeaderCorsUrls: [/https:\/\/api\.example\.com/, 'https://other-api.com'],
116
168
 
169
+ // Optional: enable XHR tracing for apps that use axios or XHR directly.
170
+ // Disabled by default because React Native's current fetch implementation is backed by XHR.
171
+ enableXhrInstrumentation: false,
172
+
117
173
  // Optional: Customize fetch instrumentation
118
174
  fetchInstrumentationOptions: {
119
175
  // Ignore network performance events (default: true)
@@ -157,10 +213,10 @@ new TracingInstrumentation({
157
213
 
158
214
  ### Automatic HTTP Tracing
159
215
 
160
- HTTP requests are automatically traced with no code changes:
216
+ `fetch()` requests are automatically traced with no code changes:
161
217
 
162
218
  ```typescript
163
- // This fetch call is automatically traced
219
+ // This fetch call is automatically traced by default.
164
220
  const response = await fetch('https://api.example.com/users');
165
221
  const data = await response.json();
166
222
 
@@ -172,6 +228,23 @@ const data = await response.json();
172
228
  // - Device metadata
173
229
  ```
174
230
 
231
+ ### Axios and Direct XHR Tracing
232
+
233
+ Axios and direct `XMLHttpRequest` calls require XHR instrumentation:
234
+
235
+ ```typescript
236
+ initializeFaro({
237
+ enableTracing: true,
238
+ tracingOptions: {
239
+ instrumentationOptions: {
240
+ enableXhrInstrumentation: true,
241
+ },
242
+ },
243
+ });
244
+ ```
245
+
246
+ If the same app also uses `fetch()` for the same backend URLs, add `ignoreUrls` to either fetch or XHR instrumentation to avoid duplicate spans.
247
+
175
248
  **Trace includes:**
176
249
 
177
250
  - `http.method`: `GET`
@@ -359,8 +432,8 @@ function performOperation() {
359
432
  │ fetch() / XHR calls
360
433
  ↓
361
434
  ┌─────────────────────────────────────────────────────────────┐
362
- │ FetchInstrumentation + XMLHttpRequestInstrumentation │
363
- │ • Intercepts fetch() and XMLHttpRequest calls │
435
+ │ FetchInstrumentation, optionally XMLHttpRequest │
436
+ │ • Intercepts fetch() by default │
364
437
  │ • Creates spans automatically │
365
438
  │ • Propagates W3C Trace Context headers │
366
439
  └──────────────────────┬──────────────────────────────────────┘
@@ -408,7 +481,7 @@ function performOperation() {
408
481
 
409
482
  ### Span Lifecycle
410
483
 
411
- 1. **Span Creation**: FetchInstrumentation or XMLHttpRequestInstrumentation creates a span when fetch()/XHR is called; W3C Trace Context headers added to the request (if URL matches `propagateTraceHeaderCorsUrls`)
484
+ 1. **Span Creation**: FetchInstrumentation creates a span when fetch() is called; XMLHttpRequestInstrumentation can be enabled for XHR/axios apps. W3C Trace Context headers are added to the request if the URL matches `propagateTraceHeaderCorsUrls`.
412
485
  2. **Enrichment**: HttpRequestMonitorSpanProcessor notifies for user action correlation; FaroMetaAttributesSpanProcessor adds session and user to spans (device/service come from resource)
413
486
  3. **Batching**: BatchSpanProcessor collects spans (max 30 spans or 1000ms delay)
414
487
  4. **Export**: FaroTraceExporter converts spans to OTLP format; also sends Faro events (e.g. `faro.tracing.fetch`) for CLIENT spans
@@ -667,10 +740,10 @@ interface TracingInstrumentationOptions {
667
740
  // Custom OTEL propagator (default: W3CTraceContextPropagator)
668
741
  propagator?: TextMapPropagator;
669
742
 
670
- // Custom OTEL context manager (default: ZoneContextManager)
743
+ // Custom OTEL context manager (default: StackContextManager)
671
744
  contextManager?: ContextManager;
672
745
 
673
- // Custom OTEL instrumentations (replaces default FetchInstrumentation)
746
+ // Custom OTEL instrumentations (replaces default fetch/XHR instrumentations)
674
747
  instrumentations?: Instrumentation[];
675
748
 
676
749
  // Custom span processor (replaces default BatchSpanProcessor)
@@ -681,6 +754,12 @@ interface TracingInstrumentationOptions {
681
754
  // URLs to propagate trace headers to
682
755
  propagateTraceHeaderCorsUrls?: Array<string | RegExp>;
683
756
 
757
+ // Enable fetch instrumentation (default: true)
758
+ enableFetchInstrumentation?: boolean;
759
+
760
+ // Enable XHR instrumentation (default: false)
761
+ enableXhrInstrumentation?: boolean;
762
+
684
763
  // Fetch instrumentation options
685
764
  fetchInstrumentationOptions?: {
686
765
  // Custom attributes function
@@ -716,6 +795,7 @@ function getDefaultOTELInstrumentations(options?: DefaultInstrumentationsOptions
716
795
  Currently returns:
717
796
 
718
797
  - `FetchInstrumentation` - Automatic HTTP tracing
798
+ - `XMLHttpRequestInstrumentation` - Optional; enabled with `enableXhrInstrumentation: true`
719
799
 
720
800
  ### faro.otel
721
801
 
@@ -62,12 +62,15 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
62
62
  Object.defineProperty(exports, "__esModule", { value: true });
63
63
  exports.TracingInstrumentation = void 0;
64
64
  var api_1 = require("@opentelemetry/api");
65
+ var core_1 = require("@opentelemetry/core");
65
66
  var instrumentation_1 = require("@opentelemetry/instrumentation");
66
67
  var resources_1 = require("@opentelemetry/resources");
67
68
  var sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
69
+ var sdk_trace_web_1 = require("@opentelemetry/sdk-trace-web");
68
70
  var semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
69
71
  var faro_core_1 = require("@grafana/faro-core");
70
72
  var faroTraceExporter_1 = require("./exporters/faroTraceExporter");
73
+ var devServerIgnoreUrls_1 = require("./instrumentations/devServerIgnoreUrls");
71
74
  var getDefaultOTELInstrumentations_1 = require("./instrumentations/getDefaultOTELInstrumentations");
72
75
  var faroMetaAttributesSpanProcessor_1 = require("./processors/faroMetaAttributesSpanProcessor");
73
76
  var httpRequestMonitorSpanProcessor_1 = require("./processors/httpRequestMonitorSpanProcessor");
@@ -113,7 +116,7 @@ var TracingInstrumentation = /** @class */ (function (_super) {
113
116
  }
114
117
  TracingInstrumentation.prototype.initialize = function () {
115
118
  var _this = this;
116
- var _a, _b, _c, _d;
119
+ var _a, _b, _c, _d, _e, _f;
117
120
  var options = this.options;
118
121
  var attributes = {};
119
122
  // App attributes
@@ -181,13 +184,28 @@ var TracingInstrumentation = /** @class */ (function (_super) {
181
184
  // Register the provider as the global tracer provider
182
185
  // This is CRITICAL for the tracer to generate real trace IDs instead of all zeros
183
186
  api_1.trace.setGlobalTracerProvider(this.provider);
184
- var _e = (_c = this.options.instrumentationOptions) !== null && _c !== void 0 ? _c : {}, propagateTraceHeaderCorsUrls = _e.propagateTraceHeaderCorsUrls, fetchInstrumentationOptions = _e.fetchInstrumentationOptions, xhrInstrumentationOptions = _e.xhrInstrumentationOptions;
187
+ // Register a global ContextManager. Without one, OTel falls back to the NoopContextManager,
188
+ // which always returns ROOT_CONTEXT — so when `@opentelemetry/instrumentation-fetch` does
189
+ // `context.with(setSpan(active(), createdSpan), () => _addHeaders(...))` the span set on the
190
+ // wrapped context is invisible inside `_addHeaders` and `propagation.inject` writes nothing.
191
+ // `StackContextManager` is pure JS (no DOM/Zone deps) and works in React Native.
192
+ api_1.context.setGlobalContextManager((_c = options.contextManager) !== null && _c !== void 0 ? _c : new sdk_trace_web_1.StackContextManager().enable());
193
+ // Register the global text-map propagator. Without this, OTel falls back to a
194
+ // NoopTextMapPropagator and `propagation.inject(...)` becomes a no-op, meaning
195
+ // `traceparent` / `tracestate` (and `baggage`) headers are never written on the
196
+ // outbound fetch/XHR — so the backend receives no context and starts a new trace.
197
+ api_1.propagation.setGlobalPropagator((_d = options.propagator) !== null && _d !== void 0 ? _d : new core_1.CompositePropagator({
198
+ propagators: [new core_1.W3CTraceContextPropagator(), new core_1.W3CBaggagePropagator()],
199
+ }));
200
+ var _g = (_e = this.options.instrumentationOptions) !== null && _e !== void 0 ? _e : {}, enableFetchInstrumentation = _g.enableFetchInstrumentation, enableXhrInstrumentation = _g.enableXhrInstrumentation, propagateTraceHeaderCorsUrls = _g.propagateTraceHeaderCorsUrls, fetchInstrumentationOptions = _g.fetchInstrumentationOptions, xhrInstrumentationOptions = _g.xhrInstrumentationOptions;
185
201
  // Get ignore URLs from transports to prevent infinite loops
186
202
  var ignoreUrls = this.getIgnoreUrls();
187
203
  // Register instrumentations
188
204
  (0, instrumentation_1.registerInstrumentations)({
189
- instrumentations: (_d = options.instrumentations) !== null && _d !== void 0 ? _d : (0, getDefaultOTELInstrumentations_1.getDefaultOTELInstrumentations)({
205
+ instrumentations: (_f = options.instrumentations) !== null && _f !== void 0 ? _f : (0, getDefaultOTELInstrumentations_1.getDefaultOTELInstrumentations)({
190
206
  ignoreUrls: ignoreUrls,
207
+ enableFetchInstrumentation: enableFetchInstrumentation,
208
+ enableXhrInstrumentation: enableXhrInstrumentation,
191
209
  propagateTraceHeaderCorsUrls: propagateTraceHeaderCorsUrls,
192
210
  fetchInstrumentationOptions: fetchInstrumentationOptions,
193
211
  xhrInstrumentationOptions: xhrInstrumentationOptions,
@@ -223,8 +241,8 @@ var TracingInstrumentation = /** @class */ (function (_super) {
223
241
  }
224
242
  return url;
225
243
  });
226
- // Return both original URLs and regex patterns for maximum coverage
227
- return __spreadArray(__spreadArray([], transportUrls, true), regexPatterns, true);
244
+ // Return dev-server, original transport URLs, and regex patterns for maximum coverage.
245
+ return __spreadArray(__spreadArray(__spreadArray([], (0, devServerIgnoreUrls_1.getReactNativeDevServerIgnoreUrls)(), true), transportUrls, true), regexPatterns, true);
228
246
  };
229
247
  /**
230
248
  * Shutdown the tracer provider
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../src/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { context, trace } from '@opentelemetry/api';\nimport type { Attributes } from '@opentelemetry/api';\nimport { registerInstrumentations } from '@opentelemetry/instrumentation';\nimport { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchSpanProcessor, BasicTracerProvider as ReactNativeTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,\n} from '@opentelemetry/semantic-conventions';\n\nimport { BaseInstrumentation, getInternalFaroFromGlobalObject, VERSION } from '@grafana/faro-core';\nimport type { Transport } from '@grafana/faro-core';\n\nimport { FaroTraceExporter } from './exporters/faroTraceExporter';\nimport { getDefaultOTELInstrumentations } from './instrumentations/getDefaultOTELInstrumentations';\nimport { FaroMetaAttributesSpanProcessor } from './processors/faroMetaAttributesSpanProcessor';\nimport { HttpRequestMonitorSpanProcessor } from './processors/httpRequestMonitorSpanProcessor';\nimport {\n ATTR_APP_VERSION,\n ATTR_DEPLOYMENT_ENVIRONMENT_NAME,\n ATTR_DEVICE_BRAND,\n ATTR_DEVICE_LOCALE,\n ATTR_DEVICE_MODEL,\n ATTR_DEVICE_OS_VERSION,\n ATTR_DEVICE_PLATFORM,\n ATTR_PROCESS_RUNTIME_NAME,\n ATTR_PROCESS_RUNTIME_VERSION,\n ATTR_SERVICE_NAMESPACE,\n ATTR_TELEMETRY_DISTRO_NAME,\n ATTR_TELEMETRY_DISTRO_VERSION,\n} from './semconv';\nimport type { TracingInstrumentationOptions } from './types';\nimport { getSamplingDecision } from './utils/sampler';\n\n// Import React Native TracerProvider\n// Note: We use the base provider since React Native doesn't have a specific one\n\n/**\n * TracingInstrumentation for React Native\n *\n * Enables distributed tracing with OpenTelemetry for React Native applications.\n *\n * IMPORTANT: Infinite loop prevention\n * - Uses internalLogger for debugging instead of console\n * - Collector URLs are added to ignoreUrls in HTTP instrumentation\n * - BatchSpanProcessor delays span export to avoid blocking\n * - No console logging during trace export\n *\n * Example usage:\n * ```ts\n * import { initializeFaro } from '@grafana/faro-react-native';\n * import { TracingInstrumentation } from '@grafana/faro-react-native-tracing';\n *\n * initializeFaro({\n * // ... other config\n * instrumentations: [\n * new TracingInstrumentation({\n * propagateTraceHeaderCorsUrls: [/https:\\\\/\\\\/my-api\\\\.com/],\n * }),\n * ],\n * });\n * ```\n */\nexport class TracingInstrumentation extends BaseInstrumentation {\n name = '@grafana/faro-react-native-tracing';\n version = VERSION;\n\n static SCHEDULED_BATCH_DELAY_MS = 1000;\n\n private provider?: BasicTracerProvider;\n\n constructor(private options: TracingInstrumentationOptions = {}) {\n super();\n }\n\n initialize(): void {\n const options = this.options;\n const attributes: Attributes = {};\n\n // App attributes\n if (this.config.app.name) {\n attributes[ATTR_SERVICE_NAME] = this.config.app.name;\n }\n\n if (this.config.app.namespace) {\n attributes[ATTR_SERVICE_NAMESPACE] = this.config.app.namespace;\n }\n\n if (this.config.app.version) {\n attributes[ATTR_SERVICE_VERSION] = this.config.app.version;\n attributes[ATTR_APP_VERSION] = this.config.app.version;\n }\n\n if (this.config.app.environment) {\n attributes[ATTR_DEPLOYMENT_ENVIRONMENT_NAME] = this.config.app.environment;\n\n /**\n * @deprecated will be removed in the future and has been replaced by ATTR_DEPLOYMENT_ENVIRONMENT_NAME (deployment.environment.name)\n * We need to keep this for compatibility with some internal services for now.\n */\n attributes[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT] = this.config.app.environment;\n }\n\n // Device/Platform attributes from React Native\n // Note: metas.value contains all meta providers, we need to check if device meta exists\n const allMetas = this.metas.value as Record<string, unknown>;\n const deviceMeta = allMetas['device'] as\n | {\n model?: string;\n brand?: string;\n osName?: string;\n osVersion?: string;\n locale?: string;\n }\n | undefined;\n\n if (deviceMeta?.model) {\n attributes[ATTR_DEVICE_MODEL] = deviceMeta.model;\n }\n\n if (deviceMeta?.brand) {\n attributes[ATTR_DEVICE_BRAND] = deviceMeta.brand;\n }\n\n if (deviceMeta?.osName) {\n attributes[ATTR_DEVICE_PLATFORM] = deviceMeta.osName;\n }\n\n if (deviceMeta?.osVersion) {\n attributes[ATTR_DEVICE_OS_VERSION] = deviceMeta.osVersion;\n }\n\n if (deviceMeta?.locale) {\n attributes[ATTR_DEVICE_LOCALE] = deviceMeta.locale;\n }\n\n attributes[ATTR_PROCESS_RUNTIME_NAME] = 'react-native';\n attributes[ATTR_PROCESS_RUNTIME_VERSION] = deviceMeta?.osVersion ?? 'unknown';\n\n attributes[ATTR_TELEMETRY_DISTRO_NAME] = 'faro-react-native-sdk';\n attributes[ATTR_TELEMETRY_DISTRO_VERSION] = VERSION;\n\n // Merge with user-provided attributes\n Object.assign(attributes, options.resourceAttributes);\n\n const resource = defaultResource().merge(resourceFromAttributes(attributes));\n\n // Create tracer provider with span processors\n this.provider = new ReactNativeTracerProvider({\n resource,\n sampler: {\n shouldSample: () => {\n return {\n decision: getSamplingDecision(this.api.getSession()),\n };\n },\n },\n spanProcessors: [\n options.spanProcessor ??\n new HttpRequestMonitorSpanProcessor(\n new FaroMetaAttributesSpanProcessor(\n new BatchSpanProcessor(new FaroTraceExporter({ api: this.api }), {\n scheduledDelayMillis: TracingInstrumentation.SCHEDULED_BATCH_DELAY_MS,\n maxExportBatchSize: 30,\n }),\n this.metas\n )\n ),\n ],\n });\n\n // Register the provider as the global tracer provider\n // This is CRITICAL for the tracer to generate real trace IDs instead of all zeros\n trace.setGlobalTracerProvider(this.provider);\n\n const { propagateTraceHeaderCorsUrls, fetchInstrumentationOptions, xhrInstrumentationOptions } =\n this.options.instrumentationOptions ?? {};\n\n // Get ignore URLs from transports to prevent infinite loops\n const ignoreUrls = this.getIgnoreUrls();\n\n // Register instrumentations\n registerInstrumentations({\n instrumentations:\n options.instrumentations ??\n getDefaultOTELInstrumentations({\n ignoreUrls,\n propagateTraceHeaderCorsUrls,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n }),\n });\n\n // Expose OTEL API on the global Faro instance for manual span creation\n // This allows users to access trace and context APIs via faro.otel\n const globalFaroInstance = getInternalFaroFromGlobalObject();\n if (globalFaroInstance) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Extending Faro instance with OTEL API\n (globalFaroInstance as any).otel = {\n trace,\n context,\n };\n }\n }\n\n /**\n * Get ignore URLs from all transports to avoid tracing collector requests\n * CRITICAL: This prevents infinite loops where trace exports trigger more traces\n */\n private getIgnoreUrls(): Array<string | RegExp> {\n // Get URLs from transports' getIgnoreUrls() method\n const transportUrls = this.transports.transports.flatMap((transport: Transport) => {\n return transport.getIgnoreUrls();\n });\n\n // Create regex patterns that match both with and without trailing slashes\n // This is critical because fetch() might add trailing slashes\n const regexPatterns = transportUrls.map((url) => {\n if (typeof url === 'string') {\n // Escape special regex characters and make trailing slash optional\n const escapedUrl = url.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`^${escapedUrl}/?$`);\n }\n return url;\n });\n\n // Return both original URLs and regex patterns for maximum coverage\n return [...transportUrls, ...regexPatterns];\n }\n\n /**\n * Shutdown the tracer provider\n */\n async shutdown(): Promise<void> {\n if (this.provider) {\n await this.provider.shutdown();\n }\n }\n}\n"]}
1
+ {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../src/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { context, propagation, trace } from '@opentelemetry/api';\nimport type { Attributes } from '@opentelemetry/api';\nimport { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';\nimport { registerInstrumentations } from '@opentelemetry/instrumentation';\nimport { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchSpanProcessor, BasicTracerProvider as ReactNativeTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport { StackContextManager } from '@opentelemetry/sdk-trace-web';\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,\n} from '@opentelemetry/semantic-conventions';\n\nimport { BaseInstrumentation, getInternalFaroFromGlobalObject, VERSION } from '@grafana/faro-core';\nimport type { Transport } from '@grafana/faro-core';\n\nimport { FaroTraceExporter } from './exporters/faroTraceExporter';\nimport { getReactNativeDevServerIgnoreUrls } from './instrumentations/devServerIgnoreUrls';\nimport { getDefaultOTELInstrumentations } from './instrumentations/getDefaultOTELInstrumentations';\nimport { FaroMetaAttributesSpanProcessor } from './processors/faroMetaAttributesSpanProcessor';\nimport { HttpRequestMonitorSpanProcessor } from './processors/httpRequestMonitorSpanProcessor';\nimport {\n ATTR_APP_VERSION,\n ATTR_DEPLOYMENT_ENVIRONMENT_NAME,\n ATTR_DEVICE_BRAND,\n ATTR_DEVICE_LOCALE,\n ATTR_DEVICE_MODEL,\n ATTR_DEVICE_OS_VERSION,\n ATTR_DEVICE_PLATFORM,\n ATTR_PROCESS_RUNTIME_NAME,\n ATTR_PROCESS_RUNTIME_VERSION,\n ATTR_SERVICE_NAMESPACE,\n ATTR_TELEMETRY_DISTRO_NAME,\n ATTR_TELEMETRY_DISTRO_VERSION,\n} from './semconv';\nimport type { TracingInstrumentationOptions } from './types';\nimport { getSamplingDecision } from './utils/sampler';\n\n// Import React Native TracerProvider\n// Note: We use the base provider since React Native doesn't have a specific one\n\n/**\n * TracingInstrumentation for React Native\n *\n * Enables distributed tracing with OpenTelemetry for React Native applications.\n *\n * IMPORTANT: Infinite loop prevention\n * - Uses internalLogger for debugging instead of console\n * - Collector URLs are added to ignoreUrls in HTTP instrumentation\n * - BatchSpanProcessor delays span export to avoid blocking\n * - No console logging during trace export\n *\n * Example usage:\n * ```ts\n * import { initializeFaro } from '@grafana/faro-react-native';\n * import { TracingInstrumentation } from '@grafana/faro-react-native-tracing';\n *\n * initializeFaro({\n * // ... other config\n * instrumentations: [\n * new TracingInstrumentation({\n * propagateTraceHeaderCorsUrls: [/https:\\\\/\\\\/my-api\\\\.com/],\n * }),\n * ],\n * });\n * ```\n */\nexport class TracingInstrumentation extends BaseInstrumentation {\n name = '@grafana/faro-react-native-tracing';\n version = VERSION;\n\n static SCHEDULED_BATCH_DELAY_MS = 1000;\n\n private provider?: BasicTracerProvider;\n\n constructor(private options: TracingInstrumentationOptions = {}) {\n super();\n }\n\n initialize(): void {\n const options = this.options;\n const attributes: Attributes = {};\n\n // App attributes\n if (this.config.app.name) {\n attributes[ATTR_SERVICE_NAME] = this.config.app.name;\n }\n\n if (this.config.app.namespace) {\n attributes[ATTR_SERVICE_NAMESPACE] = this.config.app.namespace;\n }\n\n if (this.config.app.version) {\n attributes[ATTR_SERVICE_VERSION] = this.config.app.version;\n attributes[ATTR_APP_VERSION] = this.config.app.version;\n }\n\n if (this.config.app.environment) {\n attributes[ATTR_DEPLOYMENT_ENVIRONMENT_NAME] = this.config.app.environment;\n\n /**\n * @deprecated will be removed in the future and has been replaced by ATTR_DEPLOYMENT_ENVIRONMENT_NAME (deployment.environment.name)\n * We need to keep this for compatibility with some internal services for now.\n */\n attributes[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT] = this.config.app.environment;\n }\n\n // Device/Platform attributes from React Native\n // Note: metas.value contains all meta providers, we need to check if device meta exists\n const allMetas = this.metas.value as Record<string, unknown>;\n const deviceMeta = allMetas['device'] as\n | {\n model?: string;\n brand?: string;\n osName?: string;\n osVersion?: string;\n locale?: string;\n }\n | undefined;\n\n if (deviceMeta?.model) {\n attributes[ATTR_DEVICE_MODEL] = deviceMeta.model;\n }\n\n if (deviceMeta?.brand) {\n attributes[ATTR_DEVICE_BRAND] = deviceMeta.brand;\n }\n\n if (deviceMeta?.osName) {\n attributes[ATTR_DEVICE_PLATFORM] = deviceMeta.osName;\n }\n\n if (deviceMeta?.osVersion) {\n attributes[ATTR_DEVICE_OS_VERSION] = deviceMeta.osVersion;\n }\n\n if (deviceMeta?.locale) {\n attributes[ATTR_DEVICE_LOCALE] = deviceMeta.locale;\n }\n\n attributes[ATTR_PROCESS_RUNTIME_NAME] = 'react-native';\n attributes[ATTR_PROCESS_RUNTIME_VERSION] = deviceMeta?.osVersion ?? 'unknown';\n\n attributes[ATTR_TELEMETRY_DISTRO_NAME] = 'faro-react-native-sdk';\n attributes[ATTR_TELEMETRY_DISTRO_VERSION] = VERSION;\n\n // Merge with user-provided attributes\n Object.assign(attributes, options.resourceAttributes);\n\n const resource = defaultResource().merge(resourceFromAttributes(attributes));\n\n // Create tracer provider with span processors\n this.provider = new ReactNativeTracerProvider({\n resource,\n sampler: {\n shouldSample: () => {\n return {\n decision: getSamplingDecision(this.api.getSession()),\n };\n },\n },\n spanProcessors: [\n options.spanProcessor ??\n new HttpRequestMonitorSpanProcessor(\n new FaroMetaAttributesSpanProcessor(\n new BatchSpanProcessor(new FaroTraceExporter({ api: this.api }), {\n scheduledDelayMillis: TracingInstrumentation.SCHEDULED_BATCH_DELAY_MS,\n maxExportBatchSize: 30,\n }),\n this.metas\n )\n ),\n ],\n });\n\n // Register the provider as the global tracer provider\n // This is CRITICAL for the tracer to generate real trace IDs instead of all zeros\n trace.setGlobalTracerProvider(this.provider);\n\n // Register a global ContextManager. Without one, OTel falls back to the NoopContextManager,\n // which always returns ROOT_CONTEXT — so when `@opentelemetry/instrumentation-fetch` does\n // `context.with(setSpan(active(), createdSpan), () => _addHeaders(...))` the span set on the\n // wrapped context is invisible inside `_addHeaders` and `propagation.inject` writes nothing.\n // `StackContextManager` is pure JS (no DOM/Zone deps) and works in React Native.\n context.setGlobalContextManager(options.contextManager ?? new StackContextManager().enable());\n\n // Register the global text-map propagator. Without this, OTel falls back to a\n // NoopTextMapPropagator and `propagation.inject(...)` becomes a no-op, meaning\n // `traceparent` / `tracestate` (and `baggage`) headers are never written on the\n // outbound fetch/XHR — so the backend receives no context and starts a new trace.\n propagation.setGlobalPropagator(\n options.propagator ??\n new CompositePropagator({\n propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],\n })\n );\n\n const {\n enableFetchInstrumentation,\n enableXhrInstrumentation,\n propagateTraceHeaderCorsUrls,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n } = this.options.instrumentationOptions ?? {};\n\n // Get ignore URLs from transports to prevent infinite loops\n const ignoreUrls = this.getIgnoreUrls();\n\n // Register instrumentations\n registerInstrumentations({\n instrumentations:\n options.instrumentations ??\n getDefaultOTELInstrumentations({\n ignoreUrls,\n enableFetchInstrumentation,\n enableXhrInstrumentation,\n propagateTraceHeaderCorsUrls,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n }),\n });\n\n // Expose OTEL API on the global Faro instance for manual span creation\n // This allows users to access trace and context APIs via faro.otel\n const globalFaroInstance = getInternalFaroFromGlobalObject();\n if (globalFaroInstance) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Extending Faro instance with OTEL API\n (globalFaroInstance as any).otel = {\n trace,\n context,\n };\n }\n }\n\n /**\n * Get ignore URLs from all transports to avoid tracing collector requests\n * CRITICAL: This prevents infinite loops where trace exports trigger more traces\n */\n private getIgnoreUrls(): Array<string | RegExp> {\n // Get URLs from transports' getIgnoreUrls() method\n const transportUrls = this.transports.transports.flatMap((transport: Transport) => {\n return transport.getIgnoreUrls();\n });\n\n // Create regex patterns that match both with and without trailing slashes\n // This is critical because fetch() might add trailing slashes\n const regexPatterns = transportUrls.map((url) => {\n if (typeof url === 'string') {\n // Escape special regex characters and make trailing slash optional\n const escapedUrl = url.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`^${escapedUrl}/?$`);\n }\n return url;\n });\n\n // Return dev-server, original transport URLs, and regex patterns for maximum coverage.\n return [...getReactNativeDevServerIgnoreUrls(), ...transportUrls, ...regexPatterns];\n }\n\n /**\n * Shutdown the tracer provider\n */\n async shutdown(): Promise<void> {\n if (this.provider) {\n await this.provider.shutdown();\n }\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getReactNativeDevServerIgnoreUrls = getReactNativeDevServerIgnoreUrls;
4
+ var REACT_NATIVE_DEV_SERVER_IGNORE_URLS = [
5
+ /^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|10\.0\.2\.2)(?::\d+)?\/symbolicate(?:[/?#]|$)/,
6
+ ];
7
+ function getReactNativeDevServerIgnoreUrls() {
8
+ return REACT_NATIVE_DEV_SERVER_IGNORE_URLS;
9
+ }
10
+ //# sourceMappingURL=devServerIgnoreUrls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devServerIgnoreUrls.js","sourceRoot":"","sources":["../../../src/instrumentations/devServerIgnoreUrls.ts"],"names":[],"mappings":";;AAMA,8EAEC;AAND,IAAM,mCAAmC,GAAwB;IAC/D,6FAA6F;CAC9F,CAAC;AAEF,SAAgB,iCAAiC;IAC/C,OAAO,mCAAmC,CAAC;AAC7C,CAAC","sourcesContent":["import type { MatchUrlDefinitions } from '../types';\n\nconst REACT_NATIVE_DEV_SERVER_IGNORE_URLS: MatchUrlDefinitions = [\n /^https?:\\/\\/(?:localhost|127\\.0\\.0\\.1|\\[::1\\]|10\\.0\\.2\\.2)(?::\\d+)?\\/symbolicate(?:[/?#]|$)/,\n];\n\nexport function getReactNativeDevServerIgnoreUrls(): MatchUrlDefinitions {\n return REACT_NATIVE_DEV_SERVER_IGNORE_URLS;\n}\n"]}
@@ -32,7 +32,7 @@ var instrumentationUtils_1 = require("./instrumentationUtils");
32
32
  *
33
33
  * This function creates the default OpenTelemetry instrumentations for React Native:
34
34
  * - FetchInstrumentation: Traces fetch() API calls
35
- * - XMLHttpRequestInstrumentation: Traces XMLHttpRequest and axios (which uses XHR)
35
+ * - XMLHttpRequestInstrumentation: Optional for apps that use XHR/axios directly
36
36
  *
37
37
  * IMPORTANT: Infinite loop prevention
38
38
  * - ignoreUrls is used to exclude Faro collector URLs
@@ -44,10 +44,15 @@ var instrumentationUtils_1 = require("./instrumentationUtils");
44
44
  */
45
45
  function getDefaultOTELInstrumentations(options) {
46
46
  if (options === void 0) { options = {}; }
47
- var fetchInstrumentationOptions = options.fetchInstrumentationOptions, xhrInstrumentationOptions = options.xhrInstrumentationOptions, sharedOptions = __rest(options, ["fetchInstrumentationOptions", "xhrInstrumentationOptions"]);
48
- var fetchOpts = createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions);
49
- var xhrOpts = createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions);
50
- return [new instrumentation_fetch_1.FetchInstrumentation(fetchOpts), new instrumentation_xml_http_request_1.XMLHttpRequestInstrumentation(xhrOpts)];
47
+ var _a = options.enableFetchInstrumentation, enableFetchInstrumentation = _a === void 0 ? true : _a, _b = options.enableXhrInstrumentation, enableXhrInstrumentation = _b === void 0 ? false : _b, fetchInstrumentationOptions = options.fetchInstrumentationOptions, xhrInstrumentationOptions = options.xhrInstrumentationOptions, sharedOptions = __rest(options, ["enableFetchInstrumentation", "enableXhrInstrumentation", "fetchInstrumentationOptions", "xhrInstrumentationOptions"]);
48
+ var instrumentations = [];
49
+ if (enableFetchInstrumentation) {
50
+ instrumentations.push(new instrumentation_fetch_1.FetchInstrumentation(createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions)));
51
+ }
52
+ if (enableXhrInstrumentation) {
53
+ instrumentations.push(new instrumentation_xml_http_request_1.XMLHttpRequestInstrumentation(createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions)));
54
+ }
55
+ return instrumentations;
51
56
  }
52
57
  function createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions) {
53
58
  return __assign(__assign(__assign(__assign({}, sharedOptions), {
@@ -1 +1 @@
1
- {"version":3,"file":"getDefaultOTELInstrumentations.js","sourceRoot":"","sources":["../../../src/instrumentations/getDefaultOTELInstrumentations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA4BA,wEAOC;AAlCD,8EAA4E;AAC5E,oGAAgG;AAEhG,gDAA6F;AAI7F,+DAGgC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,SAAgB,8BAA8B,CAAC,OAA4C;IAA5C,wBAAA,EAAA,YAA4C;IACjF,IAAA,2BAA2B,GAAkD,OAAO,4BAAzD,EAAE,yBAAyB,GAAuB,OAAO,0BAA9B,EAAK,aAAa,UAAK,OAAO,EAAtF,4DAA4E,CAAF,CAAa;IAE7F,IAAM,SAAS,GAAG,iCAAiC,CAAC,2BAA2B,EAAE,aAAa,CAAC,CAAC;IAChG,IAAM,OAAO,GAAG,+BAA+B,CAAC,yBAAyB,EAAE,aAAa,CAAC,CAAC;IAE1F,OAAO,CAAC,IAAI,4CAAoB,CAAC,SAAS,CAAC,EAAE,IAAI,gEAA6B,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,iCAAiC,CACxC,2BAA0F,EAC1F,aAAsC;IAEtC,+CACK,aAAa;QAChB,wDAAwD;QACxD,mBAAmB,EAAE,IAAI,KAEtB,2BAA2B;QAC9B,4BAA4B;QAC5B,2BAA2B,EAAE,IAAA,+DAAwC,EACnE,2BAA2B,aAA3B,2BAA2B,uBAA3B,2BAA2B,CAAE,2BAA2B,CACzD;QACD,0CAA0C;QAC1C,WAAW,EAAE,UAAC,IAAU,EAAE,CAAwB;YAChD,IAAI,CAAC;gBACH,IAAM,aAAa,GAAG,gBAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC;gBACrD,IACE,aAAa;oBACb,CAAC,aAAwD,aAAxD,aAAa,uBAAb,aAAa,CAA6C,QAAQ,EAAE,MAAK,2BAAe,CAAC,OAAO,EACjG,CAAC;oBACD,IAAI,CAAC,YAAY,CAAC,uBAAuB,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;oBAC/D,IAAI,CAAC,YAAY,CAAC,2BAA2B,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACzE,CAAC;YACH,CAAC;YAAC,OAAO,MAAM,EAAE,CAAC;gBAChB,oDAAoD;gBACpD,kDAAkD;YACpD,CAAC;QACH,CAAC,IACD;AACJ,CAAC;AAED,SAAS,+BAA+B,CACtC,yBAAsF,EACtF,aAAsC;IAEtC,+CACK,aAAa,KAChB,mBAAmB,EAAE,IAAI,KACtB,yBAAyB,KAC5B,2BAA2B,EAAE,IAAA,6DAAsC,EACjE,yBAAyB,aAAzB,yBAAyB,uBAAzB,yBAAyB,CAAE,2BAA2B,CACvD,IACD;AACJ,CAAC","sourcesContent":["import type { Span } from '@opentelemetry/api';\nimport { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';\nimport { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';\n\nimport { faro, type UserActionInternalInterface, UserActionState } from '@grafana/faro-core';\n\nimport type { DefaultInstrumentationsOptions, InstrumentationOption } from '../types';\n\nimport {\n fetchCustomAttributeFunctionWithDefaults,\n xhrCustomAttributeFunctionWithDefaults,\n} from './instrumentationUtils';\n\n/**\n * Get default OTEL instrumentations for React Native\n *\n * This function creates the default OpenTelemetry instrumentations for React Native:\n * - FetchInstrumentation: Traces fetch() API calls\n * - XMLHttpRequestInstrumentation: Traces XMLHttpRequest and axios (which uses XHR)\n *\n * IMPORTANT: Infinite loop prevention\n * - ignoreUrls is used to exclude Faro collector URLs\n * - ignoreNetworkEvents is true to avoid duplicate events\n * - No console logging during instrumentation\n *\n * @param options - Configuration options\n * @returns Array of OTEL instrumentations\n */\nexport function getDefaultOTELInstrumentations(options: DefaultInstrumentationsOptions = {}): InstrumentationOption[] {\n const { fetchInstrumentationOptions, xhrInstrumentationOptions, ...sharedOptions } = options;\n\n const fetchOpts = createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions);\n const xhrOpts = createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions);\n\n return [new FetchInstrumentation(fetchOpts), new XMLHttpRequestInstrumentation(xhrOpts)];\n}\n\nfunction createFetchInstrumentationOptions(\n fetchInstrumentationOptions: DefaultInstrumentationsOptions['fetchInstrumentationOptions'],\n sharedOptions: Record<string, unknown>\n) {\n return {\n ...sharedOptions,\n // Ignore network performance events to avoid duplicates\n ignoreNetworkEvents: true,\n // Keep this here to overwrite the defaults above if provided by the users\n ...fetchInstrumentationOptions,\n // Always keep this function\n applyCustomAttributesOnSpan: fetchCustomAttributeFunctionWithDefaults(\n fetchInstrumentationOptions?.applyCustomAttributesOnSpan\n ),\n // Request hook to add user action context\n requestHook: (span: Span, _: Request | RequestInit) => {\n try {\n const currentAction = faro.api.getActiveUserAction();\n if (\n currentAction &&\n (currentAction as unknown as UserActionInternalInterface)?.getState() === UserActionState.Started\n ) {\n span.setAttribute('faro.action.user.name', currentAction.name);\n span.setAttribute('faro.action.user.parentId', currentAction.parentId);\n }\n } catch (_error) {\n // Silently fail - don't log to avoid infinite loops\n // The span will just not have user action context\n }\n },\n };\n}\n\nfunction createXhrInstrumentationOptions(\n xhrInstrumentationOptions: DefaultInstrumentationsOptions['xhrInstrumentationOptions'],\n sharedOptions: Record<string, unknown>\n) {\n return {\n ...sharedOptions,\n ignoreNetworkEvents: true,\n ...xhrInstrumentationOptions,\n applyCustomAttributesOnSpan: xhrCustomAttributeFunctionWithDefaults(\n xhrInstrumentationOptions?.applyCustomAttributesOnSpan\n ),\n };\n}\n"]}
1
+ {"version":3,"file":"getDefaultOTELInstrumentations.js","sourceRoot":"","sources":["../../../src/instrumentations/getDefaultOTELInstrumentations.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA4BA,wEAwBC;AAnDD,8EAA4E;AAC5E,oGAAgG;AAEhG,gDAA6F;AAI7F,+DAGgC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,SAAgB,8BAA8B,CAAC,OAA4C;IAA5C,wBAAA,EAAA,YAA4C;IAEvF,IAAA,KAKE,OAAO,2BALwB,EAAjC,0BAA0B,mBAAG,IAAI,KAAA,EACjC,KAIE,OAAO,yBAJuB,EAAhC,wBAAwB,mBAAG,KAAK,KAAA,EAChC,2BAA2B,GAGzB,OAAO,4BAHkB,EAC3B,yBAAyB,GAEvB,OAAO,0BAFgB,EACtB,aAAa,UACd,OAAO,EANL,sHAML,CADiB,CACN;IAEZ,IAAM,gBAAgB,GAA4B,EAAE,CAAC;IAErD,IAAI,0BAA0B,EAAE,CAAC;QAC/B,gBAAgB,CAAC,IAAI,CACnB,IAAI,4CAAoB,CAAC,iCAAiC,CAAC,2BAA2B,EAAE,aAAa,CAAC,CAAC,CACxG,CAAC;IACJ,CAAC;IAED,IAAI,wBAAwB,EAAE,CAAC;QAC7B,gBAAgB,CAAC,IAAI,CACnB,IAAI,gEAA6B,CAAC,+BAA+B,CAAC,yBAAyB,EAAE,aAAa,CAAC,CAAC,CAC7G,CAAC;IACJ,CAAC;IAED,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,SAAS,iCAAiC,CACxC,2BAA0F,EAC1F,aAAsC;IAEtC,+CACK,aAAa;QAChB,wDAAwD;QACxD,mBAAmB,EAAE,IAAI,KAEtB,2BAA2B;QAC9B,4BAA4B;QAC5B,2BAA2B,EAAE,IAAA,+DAAwC,EACnE,2BAA2B,aAA3B,2BAA2B,uBAA3B,2BAA2B,CAAE,2BAA2B,CACzD;QACD,0CAA0C;QAC1C,WAAW,EAAE,UAAC,IAAU,EAAE,CAAwB;YAChD,IAAI,CAAC;gBACH,IAAM,aAAa,GAAG,gBAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC;gBACrD,IACE,aAAa;oBACb,CAAC,aAAwD,aAAxD,aAAa,uBAAb,aAAa,CAA6C,QAAQ,EAAE,MAAK,2BAAe,CAAC,OAAO,EACjG,CAAC;oBACD,IAAI,CAAC,YAAY,CAAC,uBAAuB,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;oBAC/D,IAAI,CAAC,YAAY,CAAC,2BAA2B,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACzE,CAAC;YACH,CAAC;YAAC,OAAO,MAAM,EAAE,CAAC;gBAChB,oDAAoD;gBACpD,kDAAkD;YACpD,CAAC;QACH,CAAC,IACD;AACJ,CAAC;AAED,SAAS,+BAA+B,CACtC,yBAAsF,EACtF,aAAsC;IAEtC,+CACK,aAAa,KAChB,mBAAmB,EAAE,IAAI,KACtB,yBAAyB,KAC5B,2BAA2B,EAAE,IAAA,6DAAsC,EACjE,yBAAyB,aAAzB,yBAAyB,uBAAzB,yBAAyB,CAAE,2BAA2B,CACvD,IACD;AACJ,CAAC","sourcesContent":["import type { Span } from '@opentelemetry/api';\nimport { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';\nimport { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';\n\nimport { faro, type UserActionInternalInterface, UserActionState } from '@grafana/faro-core';\n\nimport type { DefaultInstrumentationsOptions, InstrumentationOption } from '../types';\n\nimport {\n fetchCustomAttributeFunctionWithDefaults,\n xhrCustomAttributeFunctionWithDefaults,\n} from './instrumentationUtils';\n\n/**\n * Get default OTEL instrumentations for React Native\n *\n * This function creates the default OpenTelemetry instrumentations for React Native:\n * - FetchInstrumentation: Traces fetch() API calls\n * - XMLHttpRequestInstrumentation: Optional for apps that use XHR/axios directly\n *\n * IMPORTANT: Infinite loop prevention\n * - ignoreUrls is used to exclude Faro collector URLs\n * - ignoreNetworkEvents is true to avoid duplicate events\n * - No console logging during instrumentation\n *\n * @param options - Configuration options\n * @returns Array of OTEL instrumentations\n */\nexport function getDefaultOTELInstrumentations(options: DefaultInstrumentationsOptions = {}): InstrumentationOption[] {\n const {\n enableFetchInstrumentation = true,\n enableXhrInstrumentation = false,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n ...sharedOptions\n } = options;\n\n const instrumentations: InstrumentationOption[] = [];\n\n if (enableFetchInstrumentation) {\n instrumentations.push(\n new FetchInstrumentation(createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions))\n );\n }\n\n if (enableXhrInstrumentation) {\n instrumentations.push(\n new XMLHttpRequestInstrumentation(createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions))\n );\n }\n\n return instrumentations;\n}\n\nfunction createFetchInstrumentationOptions(\n fetchInstrumentationOptions: DefaultInstrumentationsOptions['fetchInstrumentationOptions'],\n sharedOptions: Record<string, unknown>\n) {\n return {\n ...sharedOptions,\n // Ignore network performance events to avoid duplicates\n ignoreNetworkEvents: true,\n // Keep this here to overwrite the defaults above if provided by the users\n ...fetchInstrumentationOptions,\n // Always keep this function\n applyCustomAttributesOnSpan: fetchCustomAttributeFunctionWithDefaults(\n fetchInstrumentationOptions?.applyCustomAttributesOnSpan\n ),\n // Request hook to add user action context\n requestHook: (span: Span, _: Request | RequestInit) => {\n try {\n const currentAction = faro.api.getActiveUserAction();\n if (\n currentAction &&\n (currentAction as unknown as UserActionInternalInterface)?.getState() === UserActionState.Started\n ) {\n span.setAttribute('faro.action.user.name', currentAction.name);\n span.setAttribute('faro.action.user.parentId', currentAction.parentId);\n }\n } catch (_error) {\n // Silently fail - don't log to avoid infinite loops\n // The span will just not have user action context\n }\n },\n };\n}\n\nfunction createXhrInstrumentationOptions(\n xhrInstrumentationOptions: DefaultInstrumentationsOptions['xhrInstrumentationOptions'],\n sharedOptions: Record<string, unknown>\n) {\n return {\n ...sharedOptions,\n ignoreNetworkEvents: true,\n ...xhrInstrumentationOptions,\n applyCustomAttributesOnSpan: xhrCustomAttributeFunctionWithDefaults(\n xhrInstrumentationOptions?.applyCustomAttributesOnSpan\n ),\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Attributes, ContextManager, TextMapPropagator } from '@opentelemetry/api';\nimport type { Instrumentation } from '@opentelemetry/instrumentation';\nimport type { FetchCustomAttributeFunction } from '@opentelemetry/instrumentation-fetch';\nimport type { XHRCustomAttributeFunction } from '@opentelemetry/instrumentation-xml-http-request';\nimport type { SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nimport type { API, Patterns } from '@grafana/faro-core';\n\nexport type InstrumentationOption = Instrumentation | Instrumentation[];\n\nexport interface FaroTraceExporterConfig {\n api: API;\n}\n\nexport interface TracingInstrumentationOptions {\n resourceAttributes?: Attributes;\n propagator?: TextMapPropagator;\n contextManager?: ContextManager;\n instrumentations?: InstrumentationOption[];\n spanProcessor?: SpanProcessor;\n instrumentationOptions?: Omit<DefaultInstrumentationsOptions, 'ignoreUrls'>;\n}\n\nexport type MatchUrlDefinitions = Patterns;\n\nexport type DefaultInstrumentationsOptions = {\n ignoreUrls?: MatchUrlDefinitions;\n propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;\n\n fetchInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n\n xhrInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: XHRCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n};\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Attributes, ContextManager, TextMapPropagator } from '@opentelemetry/api';\nimport type { Instrumentation } from '@opentelemetry/instrumentation';\nimport type { FetchCustomAttributeFunction } from '@opentelemetry/instrumentation-fetch';\nimport type { XHRCustomAttributeFunction } from '@opentelemetry/instrumentation-xml-http-request';\nimport type { SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nimport type { API, Patterns } from '@grafana/faro-core';\n\nexport type InstrumentationOption = Instrumentation | Instrumentation[];\n\nexport interface FaroTraceExporterConfig {\n api: API;\n}\n\nexport interface TracingInstrumentationOptions {\n resourceAttributes?: Attributes;\n propagator?: TextMapPropagator;\n contextManager?: ContextManager;\n instrumentations?: InstrumentationOption[];\n spanProcessor?: SpanProcessor;\n instrumentationOptions?: Omit<DefaultInstrumentationsOptions, 'ignoreUrls'>;\n}\n\nexport type MatchUrlDefinitions = Patterns;\n\nexport type DefaultInstrumentationsOptions = {\n ignoreUrls?: MatchUrlDefinitions;\n propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;\n enableFetchInstrumentation?: boolean;\n enableXhrInstrumentation?: boolean;\n\n fetchInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n\n xhrInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: XHRCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n};\n"]}
@@ -7,13 +7,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { context, trace } from '@opentelemetry/api';
10
+ import { context, propagation, trace } from '@opentelemetry/api';
11
+ import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';
11
12
  import { registerInstrumentations } from '@opentelemetry/instrumentation';
12
13
  import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
13
14
  import { BatchSpanProcessor, BasicTracerProvider as ReactNativeTracerProvider } from '@opentelemetry/sdk-trace-base';
15
+ import { StackContextManager } from '@opentelemetry/sdk-trace-web';
14
16
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, SEMRESATTRS_DEPLOYMENT_ENVIRONMENT, } from '@opentelemetry/semantic-conventions';
15
17
  import { BaseInstrumentation, getInternalFaroFromGlobalObject, VERSION } from '@grafana/faro-core';
16
18
  import { FaroTraceExporter } from './exporters/faroTraceExporter';
19
+ import { getReactNativeDevServerIgnoreUrls } from './instrumentations/devServerIgnoreUrls';
17
20
  import { getDefaultOTELInstrumentations } from './instrumentations/getDefaultOTELInstrumentations';
18
21
  import { FaroMetaAttributesSpanProcessor } from './processors/faroMetaAttributesSpanProcessor';
19
22
  import { HttpRequestMonitorSpanProcessor } from './processors/httpRequestMonitorSpanProcessor';
@@ -55,7 +58,7 @@ export class TracingInstrumentation extends BaseInstrumentation {
55
58
  this.version = VERSION;
56
59
  }
57
60
  initialize() {
58
- var _a, _b, _c, _d;
61
+ var _a, _b, _c, _d, _e, _f;
59
62
  const options = this.options;
60
63
  const attributes = {};
61
64
  // App attributes
@@ -123,13 +126,28 @@ export class TracingInstrumentation extends BaseInstrumentation {
123
126
  // Register the provider as the global tracer provider
124
127
  // This is CRITICAL for the tracer to generate real trace IDs instead of all zeros
125
128
  trace.setGlobalTracerProvider(this.provider);
126
- const { propagateTraceHeaderCorsUrls, fetchInstrumentationOptions, xhrInstrumentationOptions } = (_c = this.options.instrumentationOptions) !== null && _c !== void 0 ? _c : {};
129
+ // Register a global ContextManager. Without one, OTel falls back to the NoopContextManager,
130
+ // which always returns ROOT_CONTEXT — so when `@opentelemetry/instrumentation-fetch` does
131
+ // `context.with(setSpan(active(), createdSpan), () => _addHeaders(...))` the span set on the
132
+ // wrapped context is invisible inside `_addHeaders` and `propagation.inject` writes nothing.
133
+ // `StackContextManager` is pure JS (no DOM/Zone deps) and works in React Native.
134
+ context.setGlobalContextManager((_c = options.contextManager) !== null && _c !== void 0 ? _c : new StackContextManager().enable());
135
+ // Register the global text-map propagator. Without this, OTel falls back to a
136
+ // NoopTextMapPropagator and `propagation.inject(...)` becomes a no-op, meaning
137
+ // `traceparent` / `tracestate` (and `baggage`) headers are never written on the
138
+ // outbound fetch/XHR — so the backend receives no context and starts a new trace.
139
+ propagation.setGlobalPropagator((_d = options.propagator) !== null && _d !== void 0 ? _d : new CompositePropagator({
140
+ propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
141
+ }));
142
+ const { enableFetchInstrumentation, enableXhrInstrumentation, propagateTraceHeaderCorsUrls, fetchInstrumentationOptions, xhrInstrumentationOptions, } = (_e = this.options.instrumentationOptions) !== null && _e !== void 0 ? _e : {};
127
143
  // Get ignore URLs from transports to prevent infinite loops
128
144
  const ignoreUrls = this.getIgnoreUrls();
129
145
  // Register instrumentations
130
146
  registerInstrumentations({
131
- instrumentations: (_d = options.instrumentations) !== null && _d !== void 0 ? _d : getDefaultOTELInstrumentations({
147
+ instrumentations: (_f = options.instrumentations) !== null && _f !== void 0 ? _f : getDefaultOTELInstrumentations({
132
148
  ignoreUrls,
149
+ enableFetchInstrumentation,
150
+ enableXhrInstrumentation,
133
151
  propagateTraceHeaderCorsUrls,
134
152
  fetchInstrumentationOptions,
135
153
  xhrInstrumentationOptions,
@@ -165,8 +183,8 @@ export class TracingInstrumentation extends BaseInstrumentation {
165
183
  }
166
184
  return url;
167
185
  });
168
- // Return both original URLs and regex patterns for maximum coverage
169
- return [...transportUrls, ...regexPatterns];
186
+ // Return dev-server, original transport URLs, and regex patterns for maximum coverage.
187
+ return [...getReactNativeDevServerIgnoreUrls(), ...transportUrls, ...regexPatterns];
170
188
  }
171
189
  /**
172
190
  * Shutdown the tracer provider
@@ -1 +1 @@
1
- {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../src/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { context, trace } from '@opentelemetry/api';\nimport type { Attributes } from '@opentelemetry/api';\nimport { registerInstrumentations } from '@opentelemetry/instrumentation';\nimport { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchSpanProcessor, BasicTracerProvider as ReactNativeTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,\n} from '@opentelemetry/semantic-conventions';\n\nimport { BaseInstrumentation, getInternalFaroFromGlobalObject, VERSION } from '@grafana/faro-core';\nimport type { Transport } from '@grafana/faro-core';\n\nimport { FaroTraceExporter } from './exporters/faroTraceExporter';\nimport { getDefaultOTELInstrumentations } from './instrumentations/getDefaultOTELInstrumentations';\nimport { FaroMetaAttributesSpanProcessor } from './processors/faroMetaAttributesSpanProcessor';\nimport { HttpRequestMonitorSpanProcessor } from './processors/httpRequestMonitorSpanProcessor';\nimport {\n ATTR_APP_VERSION,\n ATTR_DEPLOYMENT_ENVIRONMENT_NAME,\n ATTR_DEVICE_BRAND,\n ATTR_DEVICE_LOCALE,\n ATTR_DEVICE_MODEL,\n ATTR_DEVICE_OS_VERSION,\n ATTR_DEVICE_PLATFORM,\n ATTR_PROCESS_RUNTIME_NAME,\n ATTR_PROCESS_RUNTIME_VERSION,\n ATTR_SERVICE_NAMESPACE,\n ATTR_TELEMETRY_DISTRO_NAME,\n ATTR_TELEMETRY_DISTRO_VERSION,\n} from './semconv';\nimport type { TracingInstrumentationOptions } from './types';\nimport { getSamplingDecision } from './utils/sampler';\n\n// Import React Native TracerProvider\n// Note: We use the base provider since React Native doesn't have a specific one\n\n/**\n * TracingInstrumentation for React Native\n *\n * Enables distributed tracing with OpenTelemetry for React Native applications.\n *\n * IMPORTANT: Infinite loop prevention\n * - Uses internalLogger for debugging instead of console\n * - Collector URLs are added to ignoreUrls in HTTP instrumentation\n * - BatchSpanProcessor delays span export to avoid blocking\n * - No console logging during trace export\n *\n * Example usage:\n * ```ts\n * import { initializeFaro } from '@grafana/faro-react-native';\n * import { TracingInstrumentation } from '@grafana/faro-react-native-tracing';\n *\n * initializeFaro({\n * // ... other config\n * instrumentations: [\n * new TracingInstrumentation({\n * propagateTraceHeaderCorsUrls: [/https:\\\\/\\\\/my-api\\\\.com/],\n * }),\n * ],\n * });\n * ```\n */\nexport class TracingInstrumentation extends BaseInstrumentation {\n name = '@grafana/faro-react-native-tracing';\n version = VERSION;\n\n static SCHEDULED_BATCH_DELAY_MS = 1000;\n\n private provider?: BasicTracerProvider;\n\n constructor(private options: TracingInstrumentationOptions = {}) {\n super();\n }\n\n initialize(): void {\n const options = this.options;\n const attributes: Attributes = {};\n\n // App attributes\n if (this.config.app.name) {\n attributes[ATTR_SERVICE_NAME] = this.config.app.name;\n }\n\n if (this.config.app.namespace) {\n attributes[ATTR_SERVICE_NAMESPACE] = this.config.app.namespace;\n }\n\n if (this.config.app.version) {\n attributes[ATTR_SERVICE_VERSION] = this.config.app.version;\n attributes[ATTR_APP_VERSION] = this.config.app.version;\n }\n\n if (this.config.app.environment) {\n attributes[ATTR_DEPLOYMENT_ENVIRONMENT_NAME] = this.config.app.environment;\n\n /**\n * @deprecated will be removed in the future and has been replaced by ATTR_DEPLOYMENT_ENVIRONMENT_NAME (deployment.environment.name)\n * We need to keep this for compatibility with some internal services for now.\n */\n attributes[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT] = this.config.app.environment;\n }\n\n // Device/Platform attributes from React Native\n // Note: metas.value contains all meta providers, we need to check if device meta exists\n const allMetas = this.metas.value as Record<string, unknown>;\n const deviceMeta = allMetas['device'] as\n | {\n model?: string;\n brand?: string;\n osName?: string;\n osVersion?: string;\n locale?: string;\n }\n | undefined;\n\n if (deviceMeta?.model) {\n attributes[ATTR_DEVICE_MODEL] = deviceMeta.model;\n }\n\n if (deviceMeta?.brand) {\n attributes[ATTR_DEVICE_BRAND] = deviceMeta.brand;\n }\n\n if (deviceMeta?.osName) {\n attributes[ATTR_DEVICE_PLATFORM] = deviceMeta.osName;\n }\n\n if (deviceMeta?.osVersion) {\n attributes[ATTR_DEVICE_OS_VERSION] = deviceMeta.osVersion;\n }\n\n if (deviceMeta?.locale) {\n attributes[ATTR_DEVICE_LOCALE] = deviceMeta.locale;\n }\n\n attributes[ATTR_PROCESS_RUNTIME_NAME] = 'react-native';\n attributes[ATTR_PROCESS_RUNTIME_VERSION] = deviceMeta?.osVersion ?? 'unknown';\n\n attributes[ATTR_TELEMETRY_DISTRO_NAME] = 'faro-react-native-sdk';\n attributes[ATTR_TELEMETRY_DISTRO_VERSION] = VERSION;\n\n // Merge with user-provided attributes\n Object.assign(attributes, options.resourceAttributes);\n\n const resource = defaultResource().merge(resourceFromAttributes(attributes));\n\n // Create tracer provider with span processors\n this.provider = new ReactNativeTracerProvider({\n resource,\n sampler: {\n shouldSample: () => {\n return {\n decision: getSamplingDecision(this.api.getSession()),\n };\n },\n },\n spanProcessors: [\n options.spanProcessor ??\n new HttpRequestMonitorSpanProcessor(\n new FaroMetaAttributesSpanProcessor(\n new BatchSpanProcessor(new FaroTraceExporter({ api: this.api }), {\n scheduledDelayMillis: TracingInstrumentation.SCHEDULED_BATCH_DELAY_MS,\n maxExportBatchSize: 30,\n }),\n this.metas\n )\n ),\n ],\n });\n\n // Register the provider as the global tracer provider\n // This is CRITICAL for the tracer to generate real trace IDs instead of all zeros\n trace.setGlobalTracerProvider(this.provider);\n\n const { propagateTraceHeaderCorsUrls, fetchInstrumentationOptions, xhrInstrumentationOptions } =\n this.options.instrumentationOptions ?? {};\n\n // Get ignore URLs from transports to prevent infinite loops\n const ignoreUrls = this.getIgnoreUrls();\n\n // Register instrumentations\n registerInstrumentations({\n instrumentations:\n options.instrumentations ??\n getDefaultOTELInstrumentations({\n ignoreUrls,\n propagateTraceHeaderCorsUrls,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n }),\n });\n\n // Expose OTEL API on the global Faro instance for manual span creation\n // This allows users to access trace and context APIs via faro.otel\n const globalFaroInstance = getInternalFaroFromGlobalObject();\n if (globalFaroInstance) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Extending Faro instance with OTEL API\n (globalFaroInstance as any).otel = {\n trace,\n context,\n };\n }\n }\n\n /**\n * Get ignore URLs from all transports to avoid tracing collector requests\n * CRITICAL: This prevents infinite loops where trace exports trigger more traces\n */\n private getIgnoreUrls(): Array<string | RegExp> {\n // Get URLs from transports' getIgnoreUrls() method\n const transportUrls = this.transports.transports.flatMap((transport: Transport) => {\n return transport.getIgnoreUrls();\n });\n\n // Create regex patterns that match both with and without trailing slashes\n // This is critical because fetch() might add trailing slashes\n const regexPatterns = transportUrls.map((url) => {\n if (typeof url === 'string') {\n // Escape special regex characters and make trailing slash optional\n const escapedUrl = url.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`^${escapedUrl}/?$`);\n }\n return url;\n });\n\n // Return both original URLs and regex patterns for maximum coverage\n return [...transportUrls, ...regexPatterns];\n }\n\n /**\n * Shutdown the tracer provider\n */\n async shutdown(): Promise<void> {\n if (this.provider) {\n await this.provider.shutdown();\n }\n }\n}\n"]}
1
+ {"version":3,"file":"instrumentation.js","sourceRoot":"","sources":["../../src/instrumentation.ts"],"names":[],"mappings":"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","sourcesContent":["import { context, propagation, trace } from '@opentelemetry/api';\nimport type { Attributes } from '@opentelemetry/api';\nimport { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';\nimport { registerInstrumentations } from '@opentelemetry/instrumentation';\nimport { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';\nimport { BatchSpanProcessor, BasicTracerProvider as ReactNativeTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';\nimport { StackContextManager } from '@opentelemetry/sdk-trace-web';\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n SEMRESATTRS_DEPLOYMENT_ENVIRONMENT,\n} from '@opentelemetry/semantic-conventions';\n\nimport { BaseInstrumentation, getInternalFaroFromGlobalObject, VERSION } from '@grafana/faro-core';\nimport type { Transport } from '@grafana/faro-core';\n\nimport { FaroTraceExporter } from './exporters/faroTraceExporter';\nimport { getReactNativeDevServerIgnoreUrls } from './instrumentations/devServerIgnoreUrls';\nimport { getDefaultOTELInstrumentations } from './instrumentations/getDefaultOTELInstrumentations';\nimport { FaroMetaAttributesSpanProcessor } from './processors/faroMetaAttributesSpanProcessor';\nimport { HttpRequestMonitorSpanProcessor } from './processors/httpRequestMonitorSpanProcessor';\nimport {\n ATTR_APP_VERSION,\n ATTR_DEPLOYMENT_ENVIRONMENT_NAME,\n ATTR_DEVICE_BRAND,\n ATTR_DEVICE_LOCALE,\n ATTR_DEVICE_MODEL,\n ATTR_DEVICE_OS_VERSION,\n ATTR_DEVICE_PLATFORM,\n ATTR_PROCESS_RUNTIME_NAME,\n ATTR_PROCESS_RUNTIME_VERSION,\n ATTR_SERVICE_NAMESPACE,\n ATTR_TELEMETRY_DISTRO_NAME,\n ATTR_TELEMETRY_DISTRO_VERSION,\n} from './semconv';\nimport type { TracingInstrumentationOptions } from './types';\nimport { getSamplingDecision } from './utils/sampler';\n\n// Import React Native TracerProvider\n// Note: We use the base provider since React Native doesn't have a specific one\n\n/**\n * TracingInstrumentation for React Native\n *\n * Enables distributed tracing with OpenTelemetry for React Native applications.\n *\n * IMPORTANT: Infinite loop prevention\n * - Uses internalLogger for debugging instead of console\n * - Collector URLs are added to ignoreUrls in HTTP instrumentation\n * - BatchSpanProcessor delays span export to avoid blocking\n * - No console logging during trace export\n *\n * Example usage:\n * ```ts\n * import { initializeFaro } from '@grafana/faro-react-native';\n * import { TracingInstrumentation } from '@grafana/faro-react-native-tracing';\n *\n * initializeFaro({\n * // ... other config\n * instrumentations: [\n * new TracingInstrumentation({\n * propagateTraceHeaderCorsUrls: [/https:\\\\/\\\\/my-api\\\\.com/],\n * }),\n * ],\n * });\n * ```\n */\nexport class TracingInstrumentation extends BaseInstrumentation {\n name = '@grafana/faro-react-native-tracing';\n version = VERSION;\n\n static SCHEDULED_BATCH_DELAY_MS = 1000;\n\n private provider?: BasicTracerProvider;\n\n constructor(private options: TracingInstrumentationOptions = {}) {\n super();\n }\n\n initialize(): void {\n const options = this.options;\n const attributes: Attributes = {};\n\n // App attributes\n if (this.config.app.name) {\n attributes[ATTR_SERVICE_NAME] = this.config.app.name;\n }\n\n if (this.config.app.namespace) {\n attributes[ATTR_SERVICE_NAMESPACE] = this.config.app.namespace;\n }\n\n if (this.config.app.version) {\n attributes[ATTR_SERVICE_VERSION] = this.config.app.version;\n attributes[ATTR_APP_VERSION] = this.config.app.version;\n }\n\n if (this.config.app.environment) {\n attributes[ATTR_DEPLOYMENT_ENVIRONMENT_NAME] = this.config.app.environment;\n\n /**\n * @deprecated will be removed in the future and has been replaced by ATTR_DEPLOYMENT_ENVIRONMENT_NAME (deployment.environment.name)\n * We need to keep this for compatibility with some internal services for now.\n */\n attributes[SEMRESATTRS_DEPLOYMENT_ENVIRONMENT] = this.config.app.environment;\n }\n\n // Device/Platform attributes from React Native\n // Note: metas.value contains all meta providers, we need to check if device meta exists\n const allMetas = this.metas.value as Record<string, unknown>;\n const deviceMeta = allMetas['device'] as\n | {\n model?: string;\n brand?: string;\n osName?: string;\n osVersion?: string;\n locale?: string;\n }\n | undefined;\n\n if (deviceMeta?.model) {\n attributes[ATTR_DEVICE_MODEL] = deviceMeta.model;\n }\n\n if (deviceMeta?.brand) {\n attributes[ATTR_DEVICE_BRAND] = deviceMeta.brand;\n }\n\n if (deviceMeta?.osName) {\n attributes[ATTR_DEVICE_PLATFORM] = deviceMeta.osName;\n }\n\n if (deviceMeta?.osVersion) {\n attributes[ATTR_DEVICE_OS_VERSION] = deviceMeta.osVersion;\n }\n\n if (deviceMeta?.locale) {\n attributes[ATTR_DEVICE_LOCALE] = deviceMeta.locale;\n }\n\n attributes[ATTR_PROCESS_RUNTIME_NAME] = 'react-native';\n attributes[ATTR_PROCESS_RUNTIME_VERSION] = deviceMeta?.osVersion ?? 'unknown';\n\n attributes[ATTR_TELEMETRY_DISTRO_NAME] = 'faro-react-native-sdk';\n attributes[ATTR_TELEMETRY_DISTRO_VERSION] = VERSION;\n\n // Merge with user-provided attributes\n Object.assign(attributes, options.resourceAttributes);\n\n const resource = defaultResource().merge(resourceFromAttributes(attributes));\n\n // Create tracer provider with span processors\n this.provider = new ReactNativeTracerProvider({\n resource,\n sampler: {\n shouldSample: () => {\n return {\n decision: getSamplingDecision(this.api.getSession()),\n };\n },\n },\n spanProcessors: [\n options.spanProcessor ??\n new HttpRequestMonitorSpanProcessor(\n new FaroMetaAttributesSpanProcessor(\n new BatchSpanProcessor(new FaroTraceExporter({ api: this.api }), {\n scheduledDelayMillis: TracingInstrumentation.SCHEDULED_BATCH_DELAY_MS,\n maxExportBatchSize: 30,\n }),\n this.metas\n )\n ),\n ],\n });\n\n // Register the provider as the global tracer provider\n // This is CRITICAL for the tracer to generate real trace IDs instead of all zeros\n trace.setGlobalTracerProvider(this.provider);\n\n // Register a global ContextManager. Without one, OTel falls back to the NoopContextManager,\n // which always returns ROOT_CONTEXT — so when `@opentelemetry/instrumentation-fetch` does\n // `context.with(setSpan(active(), createdSpan), () => _addHeaders(...))` the span set on the\n // wrapped context is invisible inside `_addHeaders` and `propagation.inject` writes nothing.\n // `StackContextManager` is pure JS (no DOM/Zone deps) and works in React Native.\n context.setGlobalContextManager(options.contextManager ?? new StackContextManager().enable());\n\n // Register the global text-map propagator. Without this, OTel falls back to a\n // NoopTextMapPropagator and `propagation.inject(...)` becomes a no-op, meaning\n // `traceparent` / `tracestate` (and `baggage`) headers are never written on the\n // outbound fetch/XHR — so the backend receives no context and starts a new trace.\n propagation.setGlobalPropagator(\n options.propagator ??\n new CompositePropagator({\n propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],\n })\n );\n\n const {\n enableFetchInstrumentation,\n enableXhrInstrumentation,\n propagateTraceHeaderCorsUrls,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n } = this.options.instrumentationOptions ?? {};\n\n // Get ignore URLs from transports to prevent infinite loops\n const ignoreUrls = this.getIgnoreUrls();\n\n // Register instrumentations\n registerInstrumentations({\n instrumentations:\n options.instrumentations ??\n getDefaultOTELInstrumentations({\n ignoreUrls,\n enableFetchInstrumentation,\n enableXhrInstrumentation,\n propagateTraceHeaderCorsUrls,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n }),\n });\n\n // Expose OTEL API on the global Faro instance for manual span creation\n // This allows users to access trace and context APIs via faro.otel\n const globalFaroInstance = getInternalFaroFromGlobalObject();\n if (globalFaroInstance) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Extending Faro instance with OTEL API\n (globalFaroInstance as any).otel = {\n trace,\n context,\n };\n }\n }\n\n /**\n * Get ignore URLs from all transports to avoid tracing collector requests\n * CRITICAL: This prevents infinite loops where trace exports trigger more traces\n */\n private getIgnoreUrls(): Array<string | RegExp> {\n // Get URLs from transports' getIgnoreUrls() method\n const transportUrls = this.transports.transports.flatMap((transport: Transport) => {\n return transport.getIgnoreUrls();\n });\n\n // Create regex patterns that match both with and without trailing slashes\n // This is critical because fetch() might add trailing slashes\n const regexPatterns = transportUrls.map((url) => {\n if (typeof url === 'string') {\n // Escape special regex characters and make trailing slash optional\n const escapedUrl = url.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n return new RegExp(`^${escapedUrl}/?$`);\n }\n return url;\n });\n\n // Return dev-server, original transport URLs, and regex patterns for maximum coverage.\n return [...getReactNativeDevServerIgnoreUrls(), ...transportUrls, ...regexPatterns];\n }\n\n /**\n * Shutdown the tracer provider\n */\n async shutdown(): Promise<void> {\n if (this.provider) {\n await this.provider.shutdown();\n }\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ const REACT_NATIVE_DEV_SERVER_IGNORE_URLS = [
2
+ /^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|10\.0\.2\.2)(?::\d+)?\/symbolicate(?:[/?#]|$)/,
3
+ ];
4
+ export function getReactNativeDevServerIgnoreUrls() {
5
+ return REACT_NATIVE_DEV_SERVER_IGNORE_URLS;
6
+ }
7
+ //# sourceMappingURL=devServerIgnoreUrls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devServerIgnoreUrls.js","sourceRoot":"","sources":["../../../src/instrumentations/devServerIgnoreUrls.ts"],"names":[],"mappings":"AAEA,MAAM,mCAAmC,GAAwB;IAC/D,6FAA6F;CAC9F,CAAC;AAEF,MAAM,UAAU,iCAAiC;IAC/C,OAAO,mCAAmC,CAAC;AAC7C,CAAC","sourcesContent":["import type { MatchUrlDefinitions } from '../types';\n\nconst REACT_NATIVE_DEV_SERVER_IGNORE_URLS: MatchUrlDefinitions = [\n /^https?:\\/\\/(?:localhost|127\\.0\\.0\\.1|\\[::1\\]|10\\.0\\.2\\.2)(?::\\d+)?\\/symbolicate(?:[/?#]|$)/,\n];\n\nexport function getReactNativeDevServerIgnoreUrls(): MatchUrlDefinitions {\n return REACT_NATIVE_DEV_SERVER_IGNORE_URLS;\n}\n"]}
@@ -18,7 +18,7 @@ import { fetchCustomAttributeFunctionWithDefaults, xhrCustomAttributeFunctionWit
18
18
  *
19
19
  * This function creates the default OpenTelemetry instrumentations for React Native:
20
20
  * - FetchInstrumentation: Traces fetch() API calls
21
- * - XMLHttpRequestInstrumentation: Traces XMLHttpRequest and axios (which uses XHR)
21
+ * - XMLHttpRequestInstrumentation: Optional for apps that use XHR/axios directly
22
22
  *
23
23
  * IMPORTANT: Infinite loop prevention
24
24
  * - ignoreUrls is used to exclude Faro collector URLs
@@ -29,10 +29,15 @@ import { fetchCustomAttributeFunctionWithDefaults, xhrCustomAttributeFunctionWit
29
29
  * @returns Array of OTEL instrumentations
30
30
  */
31
31
  export function getDefaultOTELInstrumentations(options = {}) {
32
- const { fetchInstrumentationOptions, xhrInstrumentationOptions } = options, sharedOptions = __rest(options, ["fetchInstrumentationOptions", "xhrInstrumentationOptions"]);
33
- const fetchOpts = createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions);
34
- const xhrOpts = createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions);
35
- return [new FetchInstrumentation(fetchOpts), new XMLHttpRequestInstrumentation(xhrOpts)];
32
+ const { enableFetchInstrumentation = true, enableXhrInstrumentation = false, fetchInstrumentationOptions, xhrInstrumentationOptions } = options, sharedOptions = __rest(options, ["enableFetchInstrumentation", "enableXhrInstrumentation", "fetchInstrumentationOptions", "xhrInstrumentationOptions"]);
33
+ const instrumentations = [];
34
+ if (enableFetchInstrumentation) {
35
+ instrumentations.push(new FetchInstrumentation(createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions)));
36
+ }
37
+ if (enableXhrInstrumentation) {
38
+ instrumentations.push(new XMLHttpRequestInstrumentation(createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions)));
39
+ }
40
+ return instrumentations;
36
41
  }
37
42
  function createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions) {
38
43
  return Object.assign(Object.assign(Object.assign(Object.assign({}, sharedOptions), {
@@ -1 +1 @@
1
- {"version":3,"file":"getDefaultOTELInstrumentations.js","sourceRoot":"","sources":["../../../src/instrumentations/getDefaultOTELInstrumentations.ts"],"names":[],"mappings":";;;;;;;;;;;AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,6BAA6B,EAAE,MAAM,iDAAiD,CAAC;AAEhG,OAAO,EAAE,IAAI,EAAoC,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI7F,OAAO,EACL,wCAAwC,EACxC,sCAAsC,GACvC,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,8BAA8B,CAAC,UAA0C,EAAE;IACzF,MAAM,EAAE,2BAA2B,EAAE,yBAAyB,KAAuB,OAAO,EAAzB,aAAa,UAAK,OAAO,EAAtF,4DAA4E,CAAU,CAAC;IAE7F,MAAM,SAAS,GAAG,iCAAiC,CAAC,2BAA2B,EAAE,aAAa,CAAC,CAAC;IAChG,MAAM,OAAO,GAAG,+BAA+B,CAAC,yBAAyB,EAAE,aAAa,CAAC,CAAC;IAE1F,OAAO,CAAC,IAAI,oBAAoB,CAAC,SAAS,CAAC,EAAE,IAAI,6BAA6B,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,iCAAiC,CACxC,2BAA0F,EAC1F,aAAsC;IAEtC,mEACK,aAAa;QAChB,wDAAwD;QACxD,mBAAmB,EAAE,IAAI,KAEtB,2BAA2B;QAC9B,4BAA4B;QAC5B,2BAA2B,EAAE,wCAAwC,CACnE,2BAA2B,aAA3B,2BAA2B,uBAA3B,2BAA2B,CAAE,2BAA2B,CACzD;QACD,0CAA0C;QAC1C,WAAW,EAAE,CAAC,IAAU,EAAE,CAAwB,EAAE,EAAE;YACpD,IAAI,CAAC;gBACH,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC;gBACrD,IACE,aAAa;oBACb,CAAC,aAAwD,aAAxD,aAAa,uBAAb,aAAa,CAA6C,QAAQ,EAAE,MAAK,eAAe,CAAC,OAAO,EACjG,CAAC;oBACD,IAAI,CAAC,YAAY,CAAC,uBAAuB,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;oBAC/D,IAAI,CAAC,YAAY,CAAC,2BAA2B,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACzE,CAAC;YACH,CAAC;YAAC,OAAO,MAAM,EAAE,CAAC;gBAChB,oDAAoD;gBACpD,kDAAkD;YACpD,CAAC;QACH,CAAC,IACD;AACJ,CAAC;AAED,SAAS,+BAA+B,CACtC,yBAAsF,EACtF,aAAsC;IAEtC,mEACK,aAAa,KAChB,mBAAmB,EAAE,IAAI,KACtB,yBAAyB,KAC5B,2BAA2B,EAAE,sCAAsC,CACjE,yBAAyB,aAAzB,yBAAyB,uBAAzB,yBAAyB,CAAE,2BAA2B,CACvD,IACD;AACJ,CAAC","sourcesContent":["import type { Span } from '@opentelemetry/api';\nimport { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';\nimport { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';\n\nimport { faro, type UserActionInternalInterface, UserActionState } from '@grafana/faro-core';\n\nimport type { DefaultInstrumentationsOptions, InstrumentationOption } from '../types';\n\nimport {\n fetchCustomAttributeFunctionWithDefaults,\n xhrCustomAttributeFunctionWithDefaults,\n} from './instrumentationUtils';\n\n/**\n * Get default OTEL instrumentations for React Native\n *\n * This function creates the default OpenTelemetry instrumentations for React Native:\n * - FetchInstrumentation: Traces fetch() API calls\n * - XMLHttpRequestInstrumentation: Traces XMLHttpRequest and axios (which uses XHR)\n *\n * IMPORTANT: Infinite loop prevention\n * - ignoreUrls is used to exclude Faro collector URLs\n * - ignoreNetworkEvents is true to avoid duplicate events\n * - No console logging during instrumentation\n *\n * @param options - Configuration options\n * @returns Array of OTEL instrumentations\n */\nexport function getDefaultOTELInstrumentations(options: DefaultInstrumentationsOptions = {}): InstrumentationOption[] {\n const { fetchInstrumentationOptions, xhrInstrumentationOptions, ...sharedOptions } = options;\n\n const fetchOpts = createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions);\n const xhrOpts = createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions);\n\n return [new FetchInstrumentation(fetchOpts), new XMLHttpRequestInstrumentation(xhrOpts)];\n}\n\nfunction createFetchInstrumentationOptions(\n fetchInstrumentationOptions: DefaultInstrumentationsOptions['fetchInstrumentationOptions'],\n sharedOptions: Record<string, unknown>\n) {\n return {\n ...sharedOptions,\n // Ignore network performance events to avoid duplicates\n ignoreNetworkEvents: true,\n // Keep this here to overwrite the defaults above if provided by the users\n ...fetchInstrumentationOptions,\n // Always keep this function\n applyCustomAttributesOnSpan: fetchCustomAttributeFunctionWithDefaults(\n fetchInstrumentationOptions?.applyCustomAttributesOnSpan\n ),\n // Request hook to add user action context\n requestHook: (span: Span, _: Request | RequestInit) => {\n try {\n const currentAction = faro.api.getActiveUserAction();\n if (\n currentAction &&\n (currentAction as unknown as UserActionInternalInterface)?.getState() === UserActionState.Started\n ) {\n span.setAttribute('faro.action.user.name', currentAction.name);\n span.setAttribute('faro.action.user.parentId', currentAction.parentId);\n }\n } catch (_error) {\n // Silently fail - don't log to avoid infinite loops\n // The span will just not have user action context\n }\n },\n };\n}\n\nfunction createXhrInstrumentationOptions(\n xhrInstrumentationOptions: DefaultInstrumentationsOptions['xhrInstrumentationOptions'],\n sharedOptions: Record<string, unknown>\n) {\n return {\n ...sharedOptions,\n ignoreNetworkEvents: true,\n ...xhrInstrumentationOptions,\n applyCustomAttributesOnSpan: xhrCustomAttributeFunctionWithDefaults(\n xhrInstrumentationOptions?.applyCustomAttributesOnSpan\n ),\n };\n}\n"]}
1
+ {"version":3,"file":"getDefaultOTELInstrumentations.js","sourceRoot":"","sources":["../../../src/instrumentations/getDefaultOTELInstrumentations.ts"],"names":[],"mappings":";;;;;;;;;;;AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAC5E,OAAO,EAAE,6BAA6B,EAAE,MAAM,iDAAiD,CAAC;AAEhG,OAAO,EAAE,IAAI,EAAoC,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAI7F,OAAO,EACL,wCAAwC,EACxC,sCAAsC,GACvC,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,8BAA8B,CAAC,UAA0C,EAAE;IACzF,MAAM,EACJ,0BAA0B,GAAG,IAAI,EACjC,wBAAwB,GAAG,KAAK,EAChC,2BAA2B,EAC3B,yBAAyB,KAEvB,OAAO,EADN,aAAa,UACd,OAAO,EANL,sHAML,CAAU,CAAC;IAEZ,MAAM,gBAAgB,GAA4B,EAAE,CAAC;IAErD,IAAI,0BAA0B,EAAE,CAAC;QAC/B,gBAAgB,CAAC,IAAI,CACnB,IAAI,oBAAoB,CAAC,iCAAiC,CAAC,2BAA2B,EAAE,aAAa,CAAC,CAAC,CACxG,CAAC;IACJ,CAAC;IAED,IAAI,wBAAwB,EAAE,CAAC;QAC7B,gBAAgB,CAAC,IAAI,CACnB,IAAI,6BAA6B,CAAC,+BAA+B,CAAC,yBAAyB,EAAE,aAAa,CAAC,CAAC,CAC7G,CAAC;IACJ,CAAC;IAED,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,SAAS,iCAAiC,CACxC,2BAA0F,EAC1F,aAAsC;IAEtC,mEACK,aAAa;QAChB,wDAAwD;QACxD,mBAAmB,EAAE,IAAI,KAEtB,2BAA2B;QAC9B,4BAA4B;QAC5B,2BAA2B,EAAE,wCAAwC,CACnE,2BAA2B,aAA3B,2BAA2B,uBAA3B,2BAA2B,CAAE,2BAA2B,CACzD;QACD,0CAA0C;QAC1C,WAAW,EAAE,CAAC,IAAU,EAAE,CAAwB,EAAE,EAAE;YACpD,IAAI,CAAC;gBACH,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC;gBACrD,IACE,aAAa;oBACb,CAAC,aAAwD,aAAxD,aAAa,uBAAb,aAAa,CAA6C,QAAQ,EAAE,MAAK,eAAe,CAAC,OAAO,EACjG,CAAC;oBACD,IAAI,CAAC,YAAY,CAAC,uBAAuB,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;oBAC/D,IAAI,CAAC,YAAY,CAAC,2BAA2B,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;gBACzE,CAAC;YACH,CAAC;YAAC,OAAO,MAAM,EAAE,CAAC;gBAChB,oDAAoD;gBACpD,kDAAkD;YACpD,CAAC;QACH,CAAC,IACD;AACJ,CAAC;AAED,SAAS,+BAA+B,CACtC,yBAAsF,EACtF,aAAsC;IAEtC,mEACK,aAAa,KAChB,mBAAmB,EAAE,IAAI,KACtB,yBAAyB,KAC5B,2BAA2B,EAAE,sCAAsC,CACjE,yBAAyB,aAAzB,yBAAyB,uBAAzB,yBAAyB,CAAE,2BAA2B,CACvD,IACD;AACJ,CAAC","sourcesContent":["import type { Span } from '@opentelemetry/api';\nimport { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';\nimport { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';\n\nimport { faro, type UserActionInternalInterface, UserActionState } from '@grafana/faro-core';\n\nimport type { DefaultInstrumentationsOptions, InstrumentationOption } from '../types';\n\nimport {\n fetchCustomAttributeFunctionWithDefaults,\n xhrCustomAttributeFunctionWithDefaults,\n} from './instrumentationUtils';\n\n/**\n * Get default OTEL instrumentations for React Native\n *\n * This function creates the default OpenTelemetry instrumentations for React Native:\n * - FetchInstrumentation: Traces fetch() API calls\n * - XMLHttpRequestInstrumentation: Optional for apps that use XHR/axios directly\n *\n * IMPORTANT: Infinite loop prevention\n * - ignoreUrls is used to exclude Faro collector URLs\n * - ignoreNetworkEvents is true to avoid duplicate events\n * - No console logging during instrumentation\n *\n * @param options - Configuration options\n * @returns Array of OTEL instrumentations\n */\nexport function getDefaultOTELInstrumentations(options: DefaultInstrumentationsOptions = {}): InstrumentationOption[] {\n const {\n enableFetchInstrumentation = true,\n enableXhrInstrumentation = false,\n fetchInstrumentationOptions,\n xhrInstrumentationOptions,\n ...sharedOptions\n } = options;\n\n const instrumentations: InstrumentationOption[] = [];\n\n if (enableFetchInstrumentation) {\n instrumentations.push(\n new FetchInstrumentation(createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions))\n );\n }\n\n if (enableXhrInstrumentation) {\n instrumentations.push(\n new XMLHttpRequestInstrumentation(createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions))\n );\n }\n\n return instrumentations;\n}\n\nfunction createFetchInstrumentationOptions(\n fetchInstrumentationOptions: DefaultInstrumentationsOptions['fetchInstrumentationOptions'],\n sharedOptions: Record<string, unknown>\n) {\n return {\n ...sharedOptions,\n // Ignore network performance events to avoid duplicates\n ignoreNetworkEvents: true,\n // Keep this here to overwrite the defaults above if provided by the users\n ...fetchInstrumentationOptions,\n // Always keep this function\n applyCustomAttributesOnSpan: fetchCustomAttributeFunctionWithDefaults(\n fetchInstrumentationOptions?.applyCustomAttributesOnSpan\n ),\n // Request hook to add user action context\n requestHook: (span: Span, _: Request | RequestInit) => {\n try {\n const currentAction = faro.api.getActiveUserAction();\n if (\n currentAction &&\n (currentAction as unknown as UserActionInternalInterface)?.getState() === UserActionState.Started\n ) {\n span.setAttribute('faro.action.user.name', currentAction.name);\n span.setAttribute('faro.action.user.parentId', currentAction.parentId);\n }\n } catch (_error) {\n // Silently fail - don't log to avoid infinite loops\n // The span will just not have user action context\n }\n },\n };\n}\n\nfunction createXhrInstrumentationOptions(\n xhrInstrumentationOptions: DefaultInstrumentationsOptions['xhrInstrumentationOptions'],\n sharedOptions: Record<string, unknown>\n) {\n return {\n ...sharedOptions,\n ignoreNetworkEvents: true,\n ...xhrInstrumentationOptions,\n applyCustomAttributesOnSpan: xhrCustomAttributeFunctionWithDefaults(\n xhrInstrumentationOptions?.applyCustomAttributesOnSpan\n ),\n };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Attributes, ContextManager, TextMapPropagator } from '@opentelemetry/api';\nimport type { Instrumentation } from '@opentelemetry/instrumentation';\nimport type { FetchCustomAttributeFunction } from '@opentelemetry/instrumentation-fetch';\nimport type { XHRCustomAttributeFunction } from '@opentelemetry/instrumentation-xml-http-request';\nimport type { SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nimport type { API, Patterns } from '@grafana/faro-core';\n\nexport type InstrumentationOption = Instrumentation | Instrumentation[];\n\nexport interface FaroTraceExporterConfig {\n api: API;\n}\n\nexport interface TracingInstrumentationOptions {\n resourceAttributes?: Attributes;\n propagator?: TextMapPropagator;\n contextManager?: ContextManager;\n instrumentations?: InstrumentationOption[];\n spanProcessor?: SpanProcessor;\n instrumentationOptions?: Omit<DefaultInstrumentationsOptions, 'ignoreUrls'>;\n}\n\nexport type MatchUrlDefinitions = Patterns;\n\nexport type DefaultInstrumentationsOptions = {\n ignoreUrls?: MatchUrlDefinitions;\n propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;\n\n fetchInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n\n xhrInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: XHRCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n};\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Attributes, ContextManager, TextMapPropagator } from '@opentelemetry/api';\nimport type { Instrumentation } from '@opentelemetry/instrumentation';\nimport type { FetchCustomAttributeFunction } from '@opentelemetry/instrumentation-fetch';\nimport type { XHRCustomAttributeFunction } from '@opentelemetry/instrumentation-xml-http-request';\nimport type { SpanProcessor } from '@opentelemetry/sdk-trace-base';\n\nimport type { API, Patterns } from '@grafana/faro-core';\n\nexport type InstrumentationOption = Instrumentation | Instrumentation[];\n\nexport interface FaroTraceExporterConfig {\n api: API;\n}\n\nexport interface TracingInstrumentationOptions {\n resourceAttributes?: Attributes;\n propagator?: TextMapPropagator;\n contextManager?: ContextManager;\n instrumentations?: InstrumentationOption[];\n spanProcessor?: SpanProcessor;\n instrumentationOptions?: Omit<DefaultInstrumentationsOptions, 'ignoreUrls'>;\n}\n\nexport type MatchUrlDefinitions = Patterns;\n\nexport type DefaultInstrumentationsOptions = {\n ignoreUrls?: MatchUrlDefinitions;\n propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;\n enableFetchInstrumentation?: boolean;\n enableXhrInstrumentation?: boolean;\n\n fetchInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n\n xhrInstrumentationOptions?: {\n applyCustomAttributesOnSpan?: XHRCustomAttributeFunction;\n ignoreNetworkEvents?: boolean;\n };\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import type { MatchUrlDefinitions } from '../types';
2
+ export declare function getReactNativeDevServerIgnoreUrls(): MatchUrlDefinitions;
@@ -4,7 +4,7 @@ import type { DefaultInstrumentationsOptions, InstrumentationOption } from '../t
4
4
  *
5
5
  * This function creates the default OpenTelemetry instrumentations for React Native:
6
6
  * - FetchInstrumentation: Traces fetch() API calls
7
- * - XMLHttpRequestInstrumentation: Traces XMLHttpRequest and axios (which uses XHR)
7
+ * - XMLHttpRequestInstrumentation: Optional for apps that use XHR/axios directly
8
8
  *
9
9
  * IMPORTANT: Infinite loop prevention
10
10
  * - ignoreUrls is used to exclude Faro collector URLs
@@ -20,6 +20,8 @@ export type MatchUrlDefinitions = Patterns;
20
20
  export type DefaultInstrumentationsOptions = {
21
21
  ignoreUrls?: MatchUrlDefinitions;
22
22
  propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;
23
+ enableFetchInstrumentation?: boolean;
24
+ enableXhrInstrumentation?: boolean;
23
25
  fetchInstrumentationOptions?: {
24
26
  applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;
25
27
  ignoreNetworkEvents?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grafana/faro-react-native-tracing",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Faro React Native tracing implementation with OpenTelemetry.",
5
5
  "keywords": [
6
6
  "observability",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@grafana/faro-core": "^2.2.3",
55
- "@grafana/faro-react-native": "^1.0.0",
55
+ "@grafana/faro-react-native": "^1.2.0",
56
56
  "@opentelemetry/api": "^1.9.0",
57
57
  "@opentelemetry/core": "^2.0.0",
58
58
  "@opentelemetry/instrumentation": "^0.208.0",
@@ -61,17 +61,18 @@
61
61
  "@opentelemetry/otlp-transformer": "^0.208.0",
62
62
  "@opentelemetry/resources": "^2.0.0",
63
63
  "@opentelemetry/sdk-trace-base": "^2.0.0",
64
+ "@opentelemetry/sdk-trace-web": "^2.0.0",
64
65
  "@opentelemetry/semantic-conventions": "^1.32.0"
65
66
  },
66
67
  "peerDependencies": {
67
68
  "react-native": ">=0.70.0"
68
69
  },
69
70
  "devDependencies": {
70
- "@types/node": "24.10.1",
71
- "@types/react-native": "^0.73.0"
71
+ "@types/node": "24.12.3",
72
+ "@types/react-native": "0.73.0"
72
73
  },
73
74
  "publishConfig": {
74
75
  "access": "public"
75
76
  },
76
- "gitHead": "00172e294e05bcf2faa2193d01aabc43c3cc8134"
77
+ "gitHead": "65b49f4c649e3400c74495257d33bd28d9af4b58"
77
78
  }
@@ -1,9 +1,11 @@
1
- import { context, trace } from '@opentelemetry/api';
1
+ import { context, propagation, trace } from '@opentelemetry/api';
2
2
  import type { Attributes } from '@opentelemetry/api';
3
+ import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator } from '@opentelemetry/core';
3
4
  import { registerInstrumentations } from '@opentelemetry/instrumentation';
4
5
  import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
5
6
  import { BatchSpanProcessor, BasicTracerProvider as ReactNativeTracerProvider } from '@opentelemetry/sdk-trace-base';
6
7
  import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base';
8
+ import { StackContextManager } from '@opentelemetry/sdk-trace-web';
7
9
  import {
8
10
  ATTR_SERVICE_NAME,
9
11
  ATTR_SERVICE_VERSION,
@@ -14,6 +16,7 @@ import { BaseInstrumentation, getInternalFaroFromGlobalObject, VERSION } from '@
14
16
  import type { Transport } from '@grafana/faro-core';
15
17
 
16
18
  import { FaroTraceExporter } from './exporters/faroTraceExporter';
19
+ import { getReactNativeDevServerIgnoreUrls } from './instrumentations/devServerIgnoreUrls';
17
20
  import { getDefaultOTELInstrumentations } from './instrumentations/getDefaultOTELInstrumentations';
18
21
  import { FaroMetaAttributesSpanProcessor } from './processors/faroMetaAttributesSpanProcessor';
19
22
  import { HttpRequestMonitorSpanProcessor } from './processors/httpRequestMonitorSpanProcessor';
@@ -175,8 +178,31 @@ export class TracingInstrumentation extends BaseInstrumentation {
175
178
  // This is CRITICAL for the tracer to generate real trace IDs instead of all zeros
176
179
  trace.setGlobalTracerProvider(this.provider);
177
180
 
178
- const { propagateTraceHeaderCorsUrls, fetchInstrumentationOptions, xhrInstrumentationOptions } =
179
- this.options.instrumentationOptions ?? {};
181
+ // Register a global ContextManager. Without one, OTel falls back to the NoopContextManager,
182
+ // which always returns ROOT_CONTEXT — so when `@opentelemetry/instrumentation-fetch` does
183
+ // `context.with(setSpan(active(), createdSpan), () => _addHeaders(...))` the span set on the
184
+ // wrapped context is invisible inside `_addHeaders` and `propagation.inject` writes nothing.
185
+ // `StackContextManager` is pure JS (no DOM/Zone deps) and works in React Native.
186
+ context.setGlobalContextManager(options.contextManager ?? new StackContextManager().enable());
187
+
188
+ // Register the global text-map propagator. Without this, OTel falls back to a
189
+ // NoopTextMapPropagator and `propagation.inject(...)` becomes a no-op, meaning
190
+ // `traceparent` / `tracestate` (and `baggage`) headers are never written on the
191
+ // outbound fetch/XHR — so the backend receives no context and starts a new trace.
192
+ propagation.setGlobalPropagator(
193
+ options.propagator ??
194
+ new CompositePropagator({
195
+ propagators: [new W3CTraceContextPropagator(), new W3CBaggagePropagator()],
196
+ })
197
+ );
198
+
199
+ const {
200
+ enableFetchInstrumentation,
201
+ enableXhrInstrumentation,
202
+ propagateTraceHeaderCorsUrls,
203
+ fetchInstrumentationOptions,
204
+ xhrInstrumentationOptions,
205
+ } = this.options.instrumentationOptions ?? {};
180
206
 
181
207
  // Get ignore URLs from transports to prevent infinite loops
182
208
  const ignoreUrls = this.getIgnoreUrls();
@@ -187,6 +213,8 @@ export class TracingInstrumentation extends BaseInstrumentation {
187
213
  options.instrumentations ??
188
214
  getDefaultOTELInstrumentations({
189
215
  ignoreUrls,
216
+ enableFetchInstrumentation,
217
+ enableXhrInstrumentation,
190
218
  propagateTraceHeaderCorsUrls,
191
219
  fetchInstrumentationOptions,
192
220
  xhrInstrumentationOptions,
@@ -226,8 +254,8 @@ export class TracingInstrumentation extends BaseInstrumentation {
226
254
  return url;
227
255
  });
228
256
 
229
- // Return both original URLs and regex patterns for maximum coverage
230
- return [...transportUrls, ...regexPatterns];
257
+ // Return dev-server, original transport URLs, and regex patterns for maximum coverage.
258
+ return [...getReactNativeDevServerIgnoreUrls(), ...transportUrls, ...regexPatterns];
231
259
  }
232
260
 
233
261
  /**
@@ -0,0 +1,23 @@
1
+ import { getReactNativeDevServerIgnoreUrls } from './devServerIgnoreUrls';
2
+
3
+ describe('getReactNativeDevServerIgnoreUrls', () => {
4
+ const ignoredUrlPattern = getReactNativeDevServerIgnoreUrls()[0] as RegExp;
5
+
6
+ it.each([
7
+ 'http://localhost:8081/symbolicate',
8
+ 'http://localhost:8081/symbolicate?platform=ios',
9
+ 'http://127.0.0.1:8081/symbolicate',
10
+ 'http://[::1]:8081/symbolicate',
11
+ 'http://10.0.2.2:8081/symbolicate',
12
+ ])('matches React Native dev-server symbolication URL %s', (url) => {
13
+ expect(ignoredUrlPattern.test(url)).toBe(true);
14
+ });
15
+
16
+ it.each([
17
+ 'https://api.example.com/symbolicate',
18
+ 'https://api.example.com/v1/symbolicate',
19
+ 'https://api.example.com/symbolicate-events',
20
+ ])('does not match backend symbolication-like URL %s', (url) => {
21
+ expect(ignoredUrlPattern.test(url)).toBe(false);
22
+ });
23
+ });
@@ -0,0 +1,9 @@
1
+ import type { MatchUrlDefinitions } from '../types';
2
+
3
+ const REACT_NATIVE_DEV_SERVER_IGNORE_URLS: MatchUrlDefinitions = [
4
+ /^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|10\.0\.2\.2)(?::\d+)?\/symbolicate(?:[/?#]|$)/,
5
+ ];
6
+
7
+ export function getReactNativeDevServerIgnoreUrls(): MatchUrlDefinitions {
8
+ return REACT_NATIVE_DEV_SERVER_IGNORE_URLS;
9
+ }
@@ -4,22 +4,48 @@ import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xm
4
4
  import { getDefaultOTELInstrumentations } from './getDefaultOTELInstrumentations';
5
5
 
6
6
  describe('getDefaultOTELInstrumentations', () => {
7
- it('should return an array with FetchInstrumentation and XMLHttpRequestInstrumentation', () => {
7
+ it('should return FetchInstrumentation by default', () => {
8
8
  const instrumentations = getDefaultOTELInstrumentations();
9
9
 
10
+ expect(instrumentations).toHaveLength(1);
11
+ expect(instrumentations[0]).toBeInstanceOf(FetchInstrumentation);
12
+ });
13
+
14
+ it('should allow enabling XMLHttpRequestInstrumentation', () => {
15
+ const instrumentations = getDefaultOTELInstrumentations({
16
+ enableXhrInstrumentation: true,
17
+ });
18
+
10
19
  expect(instrumentations).toHaveLength(2);
11
20
  expect(instrumentations[0]).toBeInstanceOf(FetchInstrumentation);
12
21
  expect(instrumentations[1]).toBeInstanceOf(XMLHttpRequestInstrumentation);
13
22
  });
14
23
 
24
+ it('should allow using only XMLHttpRequestInstrumentation', () => {
25
+ const instrumentations = getDefaultOTELInstrumentations({
26
+ enableFetchInstrumentation: false,
27
+ enableXhrInstrumentation: true,
28
+ });
29
+
30
+ expect(instrumentations).toHaveLength(1);
31
+ expect(instrumentations[0]).toBeInstanceOf(XMLHttpRequestInstrumentation);
32
+ });
33
+
15
34
  it('should configure ignoreUrls', () => {
16
35
  const ignoreUrls = [/test-url/];
17
36
  const instrumentations = getDefaultOTELInstrumentations({ ignoreUrls });
18
37
 
19
- expect(instrumentations).toHaveLength(2);
20
- // FetchInstrumentation should have ignoreUrls configured
38
+ expect(instrumentations).toHaveLength(1);
21
39
  expect((instrumentations[0] as any)._config.ignoreUrls).toEqual(ignoreUrls);
22
- // XMLHttpRequestInstrumentation should have ignoreUrls configured
40
+ });
41
+
42
+ it('should configure ignoreUrls for XMLHttpRequestInstrumentation', () => {
43
+ const ignoreUrls = [/test-url/];
44
+ const instrumentations = getDefaultOTELInstrumentations({
45
+ enableXhrInstrumentation: true,
46
+ ignoreUrls,
47
+ });
48
+
23
49
  expect((instrumentations[1] as any)._config.ignoreUrls).toEqual(ignoreUrls);
24
50
  });
25
51
 
@@ -56,7 +82,7 @@ describe('getDefaultOTELInstrumentations', () => {
56
82
  },
57
83
  });
58
84
 
59
- expect(instrumentations).toHaveLength(2);
85
+ expect(instrumentations).toHaveLength(1);
60
86
  // Custom function should be wrapped by our defaults
61
87
  expect((instrumentations[0] as any)._config.applyCustomAttributesOnSpan).toBeDefined();
62
88
  });
@@ -16,7 +16,7 @@ import {
16
16
  *
17
17
  * This function creates the default OpenTelemetry instrumentations for React Native:
18
18
  * - FetchInstrumentation: Traces fetch() API calls
19
- * - XMLHttpRequestInstrumentation: Traces XMLHttpRequest and axios (which uses XHR)
19
+ * - XMLHttpRequestInstrumentation: Optional for apps that use XHR/axios directly
20
20
  *
21
21
  * IMPORTANT: Infinite loop prevention
22
22
  * - ignoreUrls is used to exclude Faro collector URLs
@@ -27,12 +27,29 @@ import {
27
27
  * @returns Array of OTEL instrumentations
28
28
  */
29
29
  export function getDefaultOTELInstrumentations(options: DefaultInstrumentationsOptions = {}): InstrumentationOption[] {
30
- const { fetchInstrumentationOptions, xhrInstrumentationOptions, ...sharedOptions } = options;
30
+ const {
31
+ enableFetchInstrumentation = true,
32
+ enableXhrInstrumentation = false,
33
+ fetchInstrumentationOptions,
34
+ xhrInstrumentationOptions,
35
+ ...sharedOptions
36
+ } = options;
31
37
 
32
- const fetchOpts = createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions);
33
- const xhrOpts = createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions);
38
+ const instrumentations: InstrumentationOption[] = [];
34
39
 
35
- return [new FetchInstrumentation(fetchOpts), new XMLHttpRequestInstrumentation(xhrOpts)];
40
+ if (enableFetchInstrumentation) {
41
+ instrumentations.push(
42
+ new FetchInstrumentation(createFetchInstrumentationOptions(fetchInstrumentationOptions, sharedOptions))
43
+ );
44
+ }
45
+
46
+ if (enableXhrInstrumentation) {
47
+ instrumentations.push(
48
+ new XMLHttpRequestInstrumentation(createXhrInstrumentationOptions(xhrInstrumentationOptions, sharedOptions))
49
+ );
50
+ }
51
+
52
+ return instrumentations;
36
53
  }
37
54
 
38
55
  function createFetchInstrumentationOptions(
package/src/types.ts CHANGED
@@ -26,6 +26,8 @@ export type MatchUrlDefinitions = Patterns;
26
26
  export type DefaultInstrumentationsOptions = {
27
27
  ignoreUrls?: MatchUrlDefinitions;
28
28
  propagateTraceHeaderCorsUrls?: MatchUrlDefinitions;
29
+ enableFetchInstrumentation?: boolean;
30
+ enableXhrInstrumentation?: boolean;
29
31
 
30
32
  fetchInstrumentationOptions?: {
31
33
  applyCustomAttributesOnSpan?: FetchCustomAttributeFunction;