@devfellowship/observability 2.0.1 → 2.1.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 ADDED
@@ -0,0 +1,144 @@
1
+ # @devfellowship/observability
2
+
3
+ Browser observability SDK for React apps. Provides OpenTelemetry tracing, metrics, Sentry error tracking, Web Vitals, fetch instrumentation, error boundaries, and React Query error reporting — all via a single provider wrapper.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @devfellowship/observability
9
+ ```
10
+
11
+ ### Peer dependencies
12
+
13
+ - `react` >= 18
14
+ - `@tanstack/react-query` >= 5 (optional — enables automatic query error reporting)
15
+
16
+ ## Quick start
17
+
18
+ Wrap your app with `<ObservabilityProvider>`:
19
+
20
+ ```tsx
21
+ import { ObservabilityProvider } from "@devfellowship/observability";
22
+
23
+ function App() {
24
+ return (
25
+ <ObservabilityProvider>
26
+ <YourApp />
27
+ </ObservabilityProvider>
28
+ );
29
+ }
30
+ ```
31
+
32
+ With zero props, the provider reads configuration from Vite environment variables:
33
+
34
+ | Env var | Purpose |
35
+ |---------|---------|
36
+ | `VITE_APP_NAME` | Service name for traces/metrics |
37
+ | `VITE_APP_VERSION` | Service version |
38
+ | `VITE_OTEL_API_KEY` | API key sent as `x-api-key` header |
39
+ | `VITE_SENTRY_DSN` | Sentry DSN (enables error tracking + session replay) |
40
+ | `VITE_OTEL_ENABLED` | Set to `"true"` to enable (auto-enabled in production) |
41
+
42
+ ## Explicit configuration
43
+
44
+ All settings can be passed as props, overriding env vars:
45
+
46
+ ```tsx
47
+ <ObservabilityProvider
48
+ serviceName="my-app"
49
+ serviceVersion="1.2.0"
50
+ collectorUrl="https://otel.devfellowship.com"
51
+ apiKey="your-api-key"
52
+ sentryDsn="https://xxx@sentry.io/yyy"
53
+ environment="production"
54
+ enabled={true}
55
+ enableWebVitals={true}
56
+ enableQueryTracking={true}
57
+ enableFetchTracing={true}
58
+ >
59
+ <App />
60
+ </ObservabilityProvider>
61
+ ```
62
+
63
+ ### Props
64
+
65
+ | Prop | Type | Default | Description |
66
+ |------|------|---------|-------------|
67
+ | `serviceName` | `string` | `VITE_APP_NAME` or `"unknown"` | Service name for OTel resource |
68
+ | `serviceVersion` | `string` | `VITE_APP_VERSION` or `"0.0.0"` | Service version |
69
+ | `collectorUrl` | `string` | `"https://otel.devfellowship.com"` | OTel collector base URL |
70
+ | `apiKey` | `string` | `VITE_OTEL_API_KEY` | API key header value |
71
+ | `sentryDsn` | `string` | `VITE_SENTRY_DSN` | Sentry DSN (omit to disable Sentry) |
72
+ | `environment` | `string` | `"production"` / `"development"` | Environment label |
73
+ | `enabled` | `boolean` | `true` in production | Master kill-switch |
74
+ | `enableWebVitals` | `boolean` | `true` | Capture LCP, CLS, INP, TTFB |
75
+ | `enableQueryTracking` | `boolean` | `true` | Report React Query errors as spans |
76
+ | `enableFetchTracing` | `boolean` | `true` | Auto-trace all `fetch()` calls |
77
+
78
+ ## Utilities
79
+
80
+ ### Track custom events
81
+
82
+ ```ts
83
+ import { trackEvent } from "@devfellowship/observability";
84
+
85
+ trackEvent("button_click", { "button.id": "checkout", page: "/cart" });
86
+ ```
87
+
88
+ ### Set user context
89
+
90
+ ```ts
91
+ import { setUserId, setUserRole } from "@devfellowship/observability";
92
+
93
+ // After authentication
94
+ setUserId(user.id);
95
+ setUserRole(user.role);
96
+ ```
97
+
98
+ ### Sentry helpers
99
+
100
+ ```ts
101
+ import { captureError, setSentryUser } from "@devfellowship/observability";
102
+
103
+ captureError(new Error("something broke"), { component: "Checkout" });
104
+ setSentryUser(user.id, user.role);
105
+ ```
106
+
107
+ ### Error boundary
108
+
109
+ ```tsx
110
+ import { ErrorBoundary } from "@devfellowship/observability";
111
+
112
+ <ErrorBoundary fallback={<p>Something went wrong</p>}>
113
+ <RiskyComponent />
114
+ </ErrorBoundary>
115
+ ```
116
+
117
+ ## What's included
118
+
119
+ - **OTel traces** — `BatchSpanProcessor` → OTLP/HTTP exporter
120
+ - **OTel metrics** — `PeriodicExportingMetricReader` → OTLP/HTTP exporter
121
+ - **Fetch instrumentation** — auto-creates spans for all `fetch()` calls (collector URLs filtered)
122
+ - **Web Vitals** — LCP, CLS, INP, TTFB reported as OTel histogram metrics
123
+ - **Sentry** — error tracking, session replay (on-error), browser tracing
124
+ - **Error boundary** — catches React component errors, reports to both OTel and Sentry
125
+ - **React Query integration** — subscribes to query cache, creates error spans on failures
126
+ - **Resource attributes** — browser, OS, device type, session ID, viewport, connection type, route
127
+
128
+ ## Development
129
+
130
+ ```bash
131
+ npm install
132
+ npm run build # builds with tsup (ESM + .d.ts)
133
+ ```
134
+
135
+ ## Publishing
136
+
137
+ ```bash
138
+ npm version patch # or minor/major
139
+ npm publish
140
+ ```
141
+
142
+ ## License
143
+
144
+ MIT
package/dist/index.js CHANGED
@@ -100,7 +100,7 @@ function getDynamicAttributes() {
100
100
 
101
101
  // src/lib/otel-init.ts
102
102
  function initOtelBrowser(config) {
103
- const { serviceName, serviceVersion, collectorUrl, apiKey } = config;
103
+ const { serviceName, serviceVersion, collectorUrl, apiKey, sandboxSlug, sandboxBranch } = config;
104
104
  const headers = {};
105
105
  if (apiKey) {
106
106
  headers["x-api-key"] = apiKey;
@@ -109,7 +109,9 @@ function initOtelBrowser(config) {
109
109
  const resource = resourceFromAttributes({
110
110
  "service.name": serviceName,
111
111
  "service.version": serviceVersion,
112
- ...staticAttributes
112
+ ...staticAttributes,
113
+ ...sandboxSlug ? { "sandbox.slug": sandboxSlug } : {},
114
+ ...sandboxBranch ? { "sandbox.branch": sandboxBranch } : {}
113
115
  });
114
116
  const traceExporter = new OTLPTraceExporter({
115
117
  url: `${collectorUrl}/v1/traces`,
@@ -236,6 +238,12 @@ function initSentry(config) {
236
238
  }
237
239
  });
238
240
  Sentry.setTag("service.name", config.serviceName);
241
+ if (config.sandboxSlug) {
242
+ Sentry.setTag("sandbox.slug", config.sandboxSlug);
243
+ }
244
+ if (config.sandboxBranch) {
245
+ Sentry.setTag("sandbox.branch", config.sandboxBranch);
246
+ }
239
247
  _initialized = true;
240
248
  }
241
249
  function captureError(error, context) {
@@ -441,7 +449,9 @@ function ObservabilityProvider({
441
449
  const resolvedCollectorUrl = collectorUrl ?? "https://otel.devfellowship.com";
442
450
  const resolvedApiKey = apiKey ?? env.VITE_OTEL_API_KEY;
443
451
  const resolvedSentryDsn = sentryDsn ?? env.VITE_SENTRY_DSN;
444
- const resolvedEnvironment = environment ?? (env.PROD ? "production" : "development");
452
+ const sandboxSlug = env.VITE_SANDBOX_SLUG;
453
+ const sandboxBranch = env.VITE_SANDBOX_BRANCH;
454
+ const resolvedEnvironment = environment ?? (sandboxSlug ? "sandbox" : env.PROD ? "production" : "development");
445
455
  const isEnabled = enabled ?? (env.VITE_OTEL_ENABLED === "true" || env.PROD === true);
446
456
  useEffect3(() => {
447
457
  if (!isEnabled) return;
@@ -450,14 +460,18 @@ function ObservabilityProvider({
450
460
  dsn: resolvedSentryDsn,
451
461
  serviceName: resolvedServiceName,
452
462
  serviceVersion: resolvedServiceVersion,
453
- environment: resolvedEnvironment
463
+ environment: resolvedEnvironment,
464
+ sandboxSlug,
465
+ sandboxBranch
454
466
  });
455
467
  }
456
468
  const providers = initOtelBrowser({
457
469
  serviceName: resolvedServiceName,
458
470
  serviceVersion: resolvedServiceVersion,
459
471
  collectorUrl: resolvedCollectorUrl,
460
- apiKey: resolvedApiKey
472
+ apiKey: resolvedApiKey,
473
+ sandboxSlug,
474
+ sandboxBranch
461
475
  });
462
476
  providersRef.current = providers;
463
477
  if (enableFetchTracing) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ObservabilityProvider.tsx","../src/lib/otel-init.ts","../src/lib/resource-attributes.ts","../src/lib/fetch-instrumentation.ts","../src/lib/sentry-init.ts","../src/hooks/useWebVitals.ts","../src/hooks/useQueryErrorReporter.ts","../src/ErrorBoundary.tsx","../src/lib/track-event.ts"],"sourcesContent":["import React, { useEffect, useRef, ReactNode } from \"react\";\nimport { trace, SpanStatusCode } from \"@opentelemetry/api\";\nimport {\n initOtelBrowser,\n shutdownOtel,\n type OtelProviders,\n} from \"./lib/otel-init\";\nimport {\n instrumentFetch,\n uninstrumentFetch,\n} from \"./lib/fetch-instrumentation\";\nimport { getDynamicAttributes } from \"./lib/resource-attributes\";\nimport { initSentry, captureError } from \"./lib/sentry-init\";\nimport { useWebVitals } from \"./hooks/useWebVitals\";\nimport { useQueryErrorReporter } from \"./hooks/useQueryErrorReporter\";\nimport { ErrorBoundary } from \"./ErrorBoundary\";\n\nexport interface ObservabilityConfig {\n /** Service name reported to the collector. Falls back to VITE_APP_NAME env var. */\n serviceName?: string;\n /** Service version. Falls back to VITE_APP_VERSION or \"0.0.0\" */\n serviceVersion?: string;\n /** OTel collector base URL. Default: \"https://otel.devfellowship.com\" */\n collectorUrl?: string;\n /** API key sent as x-api-key header. Falls back to VITE_OTEL_API_KEY */\n apiKey?: string;\n /** Sentry DSN. Falls back to VITE_SENTRY_DSN */\n sentryDsn?: string;\n /** Environment label. Default: \"production\" */\n environment?: string;\n /** Master kill-switch. Falls back to VITE_OTEL_ENABLED or import.meta.env.PROD */\n enabled?: boolean;\n /** Capture Core Web Vitals. Default: true */\n enableWebVitals?: boolean;\n /** Report React Query errors as spans. Default: true */\n enableQueryTracking?: boolean;\n /** Auto-trace all fetch() calls. Default: true */\n enableFetchTracing?: boolean;\n}\n\ninterface ObservabilityProviderProps extends ObservabilityConfig {\n children: ReactNode;\n}\n\n// ---- Internal active-instrumentation wrapper ----\n\nfunction ActiveInstrumentation({\n enableWebVitals,\n enableQueryTracking,\n children,\n}: {\n enableWebVitals: boolean;\n enableQueryTracking: boolean;\n children: ReactNode;\n}) {\n if (enableWebVitals) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n useWebVitals();\n }\n\n if (enableQueryTracking) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n useQueryErrorReporter();\n }\n\n return <>{children}</>;\n}\n\n// ---- Main provider ----\n\n// Safe access to Vite env vars (no-op outside Vite)\nfunction getViteEnv(): Record<string, string | boolean | undefined> {\n try {\n return (import.meta as ImportMeta & { env: Record<string, string | boolean> }).env ?? {};\n } catch {\n return {};\n }\n}\n\nexport function ObservabilityProvider({\n serviceName,\n serviceVersion,\n collectorUrl,\n apiKey,\n sentryDsn,\n environment,\n enabled,\n enableWebVitals = true,\n enableQueryTracking = true,\n enableFetchTracing = true,\n children,\n}: ObservabilityProviderProps) {\n const providersRef = useRef<OtelProviders | null>(null);\n\n const env = getViteEnv();\n const resolvedServiceName = serviceName ?? (env.VITE_APP_NAME as string) ?? \"unknown\";\n const resolvedServiceVersion = serviceVersion ?? (env.VITE_APP_VERSION as string) ?? \"0.0.0\";\n const resolvedCollectorUrl = collectorUrl ?? \"https://otel.devfellowship.com\";\n const resolvedApiKey = apiKey ?? (env.VITE_OTEL_API_KEY as string);\n const resolvedSentryDsn = sentryDsn ?? (env.VITE_SENTRY_DSN as string);\n const resolvedEnvironment = environment ?? (env.PROD ? \"production\" : \"development\");\n const isEnabled = enabled ?? (env.VITE_OTEL_ENABLED === \"true\" || env.PROD === true);\n\n useEffect(() => {\n if (!isEnabled) return;\n\n // Initialize Sentry (if DSN provided)\n if (resolvedSentryDsn) {\n initSentry({\n dsn: resolvedSentryDsn,\n serviceName: resolvedServiceName,\n serviceVersion: resolvedServiceVersion,\n environment: resolvedEnvironment,\n });\n }\n\n // Initialize OTel SDK\n const providers = initOtelBrowser({\n serviceName: resolvedServiceName,\n serviceVersion: resolvedServiceVersion,\n collectorUrl: resolvedCollectorUrl,\n apiKey: resolvedApiKey,\n });\n providersRef.current = providers;\n\n // Fetch instrumentation\n if (enableFetchTracing) {\n const tracer = trace.getTracer(\"@dfl/observability\");\n instrumentFetch(tracer);\n }\n\n // Global error handlers\n const tracer = trace.getTracer(\"@dfl/observability\");\n\n const handleWindowError = (event: ErrorEvent) => {\n tracer.startActiveSpan(\"window.onerror\", (span) => {\n const dynamicAttrs = getDynamicAttributes();\n for (const [key, value] of Object.entries(dynamicAttrs)) {\n span.setAttribute(key, value);\n }\n\n span.setAttribute(\"error.message\", event.message);\n span.setAttribute(\"error.source\", event.filename ?? \"\");\n span.setAttribute(\"error.lineno\", event.lineno ?? 0);\n span.setAttribute(\"error.colno\", event.colno ?? 0);\n\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: event.message,\n });\n\n if (event.error instanceof Error) {\n span.recordException(event.error);\n captureError(event.error, {\n source: event.filename ?? \"\",\n lineno: String(event.lineno ?? 0),\n });\n }\n\n span.end();\n });\n };\n\n const handleUnhandledRejection = (event: PromiseRejectionEvent) => {\n tracer.startActiveSpan(\"unhandled-rejection\", (span) => {\n const dynamicAttrs = getDynamicAttributes();\n for (const [key, value] of Object.entries(dynamicAttrs)) {\n span.setAttribute(key, value);\n }\n\n const reason = event.reason;\n const message =\n reason instanceof Error ? reason.message : String(reason);\n\n span.setAttribute(\"error.message\", message);\n span.setAttribute(\"error.type\", \"unhandled_promise_rejection\");\n\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n\n if (reason instanceof Error) {\n span.recordException(reason);\n captureError(reason, { type: \"unhandled_promise_rejection\" });\n }\n\n span.end();\n });\n };\n\n window.addEventListener(\"error\", handleWindowError);\n window.addEventListener(\"unhandledrejection\", handleUnhandledRejection);\n\n return () => {\n window.removeEventListener(\"error\", handleWindowError);\n window.removeEventListener(\"unhandledrejection\", handleUnhandledRejection);\n\n if (enableFetchTracing) {\n uninstrumentFetch();\n }\n\n if (providersRef.current) {\n shutdownOtel(providersRef.current);\n providersRef.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isEnabled]);\n\n // No-op mode: just render children without any instrumentation\n if (!isEnabled) {\n return <>{children}</>;\n }\n\n return (\n <ErrorBoundary>\n <ActiveInstrumentation\n enableWebVitals={enableWebVitals}\n enableQueryTracking={enableQueryTracking}\n >\n {children}\n </ActiveInstrumentation>\n </ErrorBoundary>\n );\n}\n","import { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport {\n WebTracerProvider,\n BatchSpanProcessor,\n} from \"@opentelemetry/sdk-trace-web\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n MeterProvider,\n PeriodicExportingMetricReader,\n} from \"@opentelemetry/sdk-metrics\";\nimport { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-http\";\nimport { trace, metrics } from \"@opentelemetry/api\";\nimport { getStaticAttributes } from \"./resource-attributes\";\n\nexport interface OtelBrowserConfig {\n serviceName: string;\n serviceVersion: string;\n collectorUrl: string;\n apiKey?: string;\n}\n\nexport interface OtelProviders {\n tracerProvider: WebTracerProvider;\n meterProvider: MeterProvider;\n}\n\nexport function initOtelBrowser(config: OtelBrowserConfig): OtelProviders {\n const { serviceName, serviceVersion, collectorUrl, apiKey } = config;\n\n const headers: Record<string, string> = {};\n if (apiKey) {\n headers[\"x-api-key\"] = apiKey;\n }\n\n const staticAttributes = getStaticAttributes();\n\n const resource = resourceFromAttributes({\n \"service.name\": serviceName,\n \"service.version\": serviceVersion,\n ...staticAttributes,\n });\n\n // --- Traces ---\n const traceExporter = new OTLPTraceExporter({\n url: `${collectorUrl}/v1/traces`,\n headers,\n });\n\n const tracerProvider = new WebTracerProvider({\n resource,\n spanProcessors: [new BatchSpanProcessor(traceExporter)],\n });\n\n tracerProvider.register();\n\n // --- Metrics ---\n const metricExporter = new OTLPMetricExporter({\n url: `${collectorUrl}/v1/metrics`,\n headers,\n });\n\n const metricReader = new PeriodicExportingMetricReader({\n exporter: metricExporter,\n exportIntervalMillis: 60_000,\n });\n\n const meterProvider = new MeterProvider({\n resource,\n readers: [metricReader],\n });\n\n metrics.setGlobalMeterProvider(meterProvider);\n\n // Store the collector URL globally so the fetch instrumentation can filter it\n (globalThis as Record<string, unknown>).__otelCollectorUrl = collectorUrl;\n\n return { tracerProvider, meterProvider };\n}\n\nexport function shutdownOtel(providers: OtelProviders): void {\n const { tracerProvider, meterProvider } = providers;\n\n trace.disable();\n metrics.disable();\n\n tracerProvider.shutdown().catch((err) => {\n console.warn(\"[observability] tracer shutdown error:\", err);\n });\n\n meterProvider.shutdown().catch((err) => {\n console.warn(\"[observability] meter shutdown error:\", err);\n });\n}\n","/**\n * Auto-collected resource attributes for browser observability.\n *\n * Static attributes are gathered once at init time.\n * Dynamic attributes change during the session (e.g. route, viewport).\n * User attributes are set after authentication completes.\n */\n\n// ---- Module-level mutable state for post-auth enrichment ----\nlet _userId: string | undefined;\nlet _userRole: string | undefined;\n\nexport function setUserId(id: string): void {\n _userId = id;\n}\n\nexport function setUserRole(role: string): void {\n _userRole = role;\n}\n\n// ---- Session ID (stable per tab session) ----\nfunction getSessionId(): string {\n const key = \"__otel_session_id\";\n let id = sessionStorage.getItem(key);\n if (!id) {\n id = crypto.randomUUID();\n sessionStorage.setItem(key, id);\n }\n return id;\n}\n\n// ---- Browser detection (lightweight, no external lib) ----\ninterface BrowserInfo {\n name: string;\n version: string;\n}\n\nfunction detectBrowser(): BrowserInfo {\n const ua = navigator.userAgent;\n\n if (ua.includes(\"Firefox/\")) {\n const match = ua.match(/Firefox\\/([\\d.]+)/);\n return { name: \"Firefox\", version: match?.[1] ?? \"unknown\" };\n }\n if (ua.includes(\"Edg/\")) {\n const match = ua.match(/Edg\\/([\\d.]+)/);\n return { name: \"Edge\", version: match?.[1] ?? \"unknown\" };\n }\n if (ua.includes(\"Chrome/\") && !ua.includes(\"Edg/\")) {\n const match = ua.match(/Chrome\\/([\\d.]+)/);\n return { name: \"Chrome\", version: match?.[1] ?? \"unknown\" };\n }\n if (ua.includes(\"Safari/\") && !ua.includes(\"Chrome/\")) {\n const match = ua.match(/Version\\/([\\d.]+)/);\n return { name: \"Safari\", version: match?.[1] ?? \"unknown\" };\n }\n\n return { name: \"unknown\", version: \"unknown\" };\n}\n\n// ---- OS detection ----\nfunction detectOS(): string {\n const ua = navigator.userAgent;\n if (ua.includes(\"Win\")) return \"Windows\";\n if (ua.includes(\"Mac\")) return \"macOS\";\n if (ua.includes(\"Linux\")) return \"Linux\";\n if (ua.includes(\"Android\")) return \"Android\";\n if (/iPhone|iPad|iPod/.test(ua)) return \"iOS\";\n return \"unknown\";\n}\n\n// ---- Device type via viewport heuristic ----\nfunction detectDeviceType(): string {\n const width = window.innerWidth;\n if (width < 768) return \"mobile\";\n if (width < 1024) return \"tablet\";\n return \"desktop\";\n}\n\n// ---- Connection info ----\nfunction getEffectiveConnectionType(): string | undefined {\n const nav = navigator as Navigator & {\n connection?: { effectiveType?: string };\n };\n return nav.connection?.effectiveType;\n}\n\n// ---- Public API ----\n\n/**\n * Attributes that are stable for the lifetime of the page.\n * Collected once during OTel SDK initialization.\n */\nexport function getStaticAttributes(): Record<string, string> {\n const browser = detectBrowser();\n const attrs: Record<string, string> = {\n \"browser.name\": browser.name,\n \"browser.version\": browser.version,\n \"os.type\": detectOS(),\n \"device.type\": detectDeviceType(),\n \"session.id\": getSessionId(),\n };\n\n return attrs;\n}\n\n/**\n * Attributes that may change between spans (viewport, route, user, connection).\n * Called per-span or at strategic points.\n */\nexport function getDynamicAttributes(): Record<string, string | number> {\n const attrs: Record<string, string | number> = {\n \"viewport.width\": window.innerWidth,\n \"viewport.height\": window.innerHeight,\n };\n\n if (_userId) attrs[\"user.id\"] = _userId;\n if (_userRole) attrs[\"user.role\"] = _userRole;\n\n const connType = getEffectiveConnectionType();\n if (connType) attrs[\"connection.effectiveType\"] = connType;\n\n // page.route is typically set by the ObservabilityProvider via React Router\n const route = (globalThis as Record<string, unknown>).__otelCurrentRoute;\n if (typeof route === \"string\") attrs[\"page.route\"] = route;\n\n return attrs;\n}\n","import { Tracer, SpanKind, SpanStatusCode } from \"@opentelemetry/api\";\nimport { getDynamicAttributes } from \"./resource-attributes\";\n\ntype NativeFetch = typeof globalThis.fetch;\n\nlet _originalFetch: NativeFetch | null = null;\n\n/**\n * Monkey-patches `window.fetch` to automatically create OTel spans for\n * every outgoing HTTP request.\n *\n * Collector URLs are filtered out to prevent infinite export loops.\n */\nexport function instrumentFetch(tracer: Tracer): void {\n if (_originalFetch) {\n // Already instrumented — avoid double-wrapping.\n return;\n }\n\n _originalFetch = window.fetch.bind(window);\n\n window.fetch = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const url =\n input instanceof Request ? input.url : input instanceof URL ? input.href : input;\n const method = init?.method ?? (input instanceof Request ? input.method : \"GET\");\n\n // Filter out OTel collector traffic to avoid infinite loops.\n const collectorUrl = (globalThis as Record<string, unknown>).__otelCollectorUrl;\n if (typeof collectorUrl === \"string\" && url.startsWith(collectorUrl)) {\n return _originalFetch!(input, init);\n }\n\n const dynamicAttrs = getDynamicAttributes();\n\n return tracer.startActiveSpan(\n `HTTP ${method.toUpperCase()}`,\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"http.method\": method.toUpperCase(),\n \"http.url\": url,\n ...dynamicAttrs,\n },\n },\n async (span) => {\n try {\n const response = await _originalFetch!(input, init);\n\n span.setAttribute(\"http.status_code\", response.status);\n\n const contentLength = response.headers.get(\"content-length\");\n if (contentLength) {\n span.setAttribute(\n \"http.response_content_length\",\n parseInt(contentLength, 10),\n );\n }\n\n if (response.status >= 400) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: `HTTP ${response.status}`,\n });\n }\n\n return response;\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"Network request failed\";\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n span.recordException(error instanceof Error ? error : new Error(message));\n throw error;\n } finally {\n span.end();\n }\n },\n );\n };\n}\n\n/**\n * Restores the original `window.fetch`, removing instrumentation.\n */\nexport function uninstrumentFetch(): void {\n if (_originalFetch) {\n window.fetch = _originalFetch;\n _originalFetch = null;\n }\n}\n","import * as Sentry from \"@sentry/react\";\n\nexport interface SentryConfig {\n dsn: string;\n serviceName: string;\n serviceVersion: string;\n environment: string;\n}\n\nlet _initialized = false;\n\nexport function initSentry(config: SentryConfig): void {\n if (_initialized) return;\n\n Sentry.init({\n dsn: config.dsn,\n environment: config.environment,\n release: `${config.serviceName}@${config.serviceVersion}`,\n integrations: [\n Sentry.browserTracingIntegration(),\n Sentry.replayIntegration({ maskAllText: false, blockAllMedia: false }),\n ],\n tracesSampleRate: 0.2,\n replaysSessionSampleRate: 0.0,\n replaysOnErrorSampleRate: 1.0,\n beforeSend(event) {\n event.tags = {\n ...event.tags,\n \"service.name\": config.serviceName,\n };\n return event;\n },\n });\n\n Sentry.setTag(\"service.name\", config.serviceName);\n _initialized = true;\n}\n\nexport function captureError(error: Error, context?: Record<string, string>): void {\n if (!_initialized) return;\n Sentry.withScope((scope) => {\n if (context) {\n for (const [key, value] of Object.entries(context)) {\n scope.setExtra(key, value);\n }\n }\n Sentry.captureException(error);\n });\n}\n\nexport function setSentryUser(id: string, role?: string): void {\n if (!_initialized) return;\n Sentry.setUser({ id, ...(role ? { role } : {}) });\n}\n","import { useEffect, useRef } from \"react\";\nimport { metrics } from \"@opentelemetry/api\";\nimport type { Metric } from \"web-vitals\";\n\n/**\n * Captures Core Web Vitals (LCP, FID, CLS, INP, TTFB) and reports them\n * as OTel histogram metrics via the global MeterProvider.\n *\n * Runs once on mount. The `web-vitals` callbacks fire at most once per\n * metric per page load, so no debouncing is needed.\n */\nexport function useWebVitals(): void {\n const initialized = useRef(false);\n\n useEffect(() => {\n if (initialized.current) return;\n initialized.current = true;\n\n const meter = metrics.getMeter(\"web-vitals\");\n\n const lcpHistogram = meter.createHistogram(\"web_vital.lcp\", {\n description: \"Largest Contentful Paint (ms)\",\n unit: \"ms\",\n });\n const clsHistogram = meter.createHistogram(\"web_vital.cls\", {\n description: \"Cumulative Layout Shift\",\n unit: \"\",\n });\n const inpHistogram = meter.createHistogram(\"web_vital.inp\", {\n description: \"Interaction to Next Paint (ms)\",\n unit: \"ms\",\n });\n const ttfbHistogram = meter.createHistogram(\"web_vital.ttfb\", {\n description: \"Time to First Byte (ms)\",\n unit: \"ms\",\n });\n\n const report = (\n histogram: ReturnType<typeof meter.createHistogram>,\n metric: Metric,\n ) => {\n histogram.record(metric.value, {\n \"metric.id\": metric.id,\n \"metric.rating\": metric.rating,\n });\n };\n\n // Dynamic import so tree-shaking can drop web-vitals entirely when unused.\n import(\"web-vitals\").then(({ onLCP, onCLS, onINP, onTTFB }) => {\n onLCP((m) => report(lcpHistogram, m));\n onCLS((m) => report(clsHistogram, m));\n onINP((m) => report(inpHistogram, m));\n onTTFB((m) => report(ttfbHistogram, m));\n });\n }, []);\n}\n","import { useEffect } from \"react\";\nimport { useQueryClient, type QueryClient } from \"@tanstack/react-query\";\nimport { trace, SpanStatusCode } from \"@opentelemetry/api\";\nimport { getDynamicAttributes } from \"../lib/resource-attributes\";\n\n/**\n * Subscribes to the React Query cache and creates OTel error spans\n * whenever a query transitions to the `error` state.\n *\n * Safe to use outside QueryClientProvider — gracefully no-ops.\n */\nexport function useQueryErrorReporter(): void {\n let queryClient: QueryClient | null = null;\n try {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n queryClient = useQueryClient();\n } catch {\n // Not inside a QueryClientProvider — query error reporting disabled\n }\n\n useEffect(() => {\n if (!queryClient) return;\n const tracer = trace.getTracer(\"@dfl/observability\");\n\n const unsubscribe = queryClient.getQueryCache().subscribe((event) => {\n if (event.type === \"updated\" && event.action.type === \"error\") {\n const query = event.query;\n const error = query.state.error;\n\n tracer.startActiveSpan(\"react-query-error\", (span) => {\n const dynamicAttrs = getDynamicAttributes();\n for (const [key, value] of Object.entries(dynamicAttrs)) {\n span.setAttribute(key, value);\n }\n\n span.setAttribute(\"query.key\", JSON.stringify(query.queryKey));\n span.setAttribute(\n \"error.message\",\n error instanceof Error ? error.message : String(error),\n );\n span.setAttribute(\n \"error.type\",\n error instanceof Error ? error.constructor.name : typeof error,\n );\n\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message:\n error instanceof Error ? error.message : \"React Query error\",\n });\n\n if (error instanceof Error) {\n span.recordException(error);\n }\n\n span.end();\n });\n }\n });\n\n return () => {\n unsubscribe();\n };\n }, [queryClient]);\n}\n","import React, { Component, ErrorInfo, ReactNode } from \"react\";\nimport { trace, SpanStatusCode } from \"@opentelemetry/api\";\nimport { captureError } from \"./lib/sentry-init\";\n\ninterface ErrorBoundaryProps {\n children: ReactNode;\n fallback?: ReactNode;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * React error boundary that reports uncaught component errors to OTel\n * as error spans before rendering a fallback UI.\n */\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo): void {\n const tracer = trace.getTracer(\"@dfl/observability\");\n\n tracer.startActiveSpan(\"react-error-boundary\", (span) => {\n span.setAttribute(\"error.message\", error.message);\n span.setAttribute(\"error.stack\", error.stack ?? \"\");\n span.setAttribute(\n \"error.component_stack\",\n errorInfo.componentStack ?? \"\",\n );\n\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n\n span.recordException(error);\n span.end();\n });\n\n captureError(error, {\n component_stack: errorInfo.componentStack ?? \"\",\n });\n }\n\n private handleReload = (): void => {\n window.location.reload();\n };\n\n render(): ReactNode {\n if (this.state.hasError) {\n if (this.props.fallback) {\n return this.props.fallback;\n }\n\n return (\n <div className=\"flex min-h-[200px] flex-col items-center justify-center gap-4 rounded-lg border border-red-200 bg-red-50 p-8 text-center dark:border-red-900 dark:bg-red-950\">\n <h2 className=\"text-lg font-semibold text-red-800 dark:text-red-200\">\n Something went wrong\n </h2>\n <p className=\"max-w-md text-sm text-red-600 dark:text-red-400\">\n An unexpected error occurred. Please try reloading the page.\n </p>\n <button\n onClick={this.handleReload}\n className=\"rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:bg-red-700 dark:hover:bg-red-600\"\n >\n Reload page\n </button>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n","import { trace, SpanStatusCode } from \"@opentelemetry/api\";\nimport { getDynamicAttributes } from \"./resource-attributes\";\n\n/**\n * Emit a custom observability event as an OTel span.\n *\n * Uses the global tracer registered by the ObservabilityProvider,\n * so it can be called from anywhere in the app without prop-drilling.\n *\n * @example\n * ```ts\n * trackEvent(\"button_click\", { \"button.id\": \"checkout\", \"page\": \"/cart\" });\n * ```\n */\nexport function trackEvent(\n name: string,\n attributes?: Record<string, string | number | boolean>,\n): void {\n const tracer = trace.getTracer(\"@dfl/observability\");\n\n tracer.startActiveSpan(name, (span) => {\n const dynamicAttrs = getDynamicAttributes();\n\n span.setAttribute(\"event.name\", name);\n\n // Attach dynamic resource attributes (viewport, user, route, etc.)\n for (const [key, value] of Object.entries(dynamicAttrs)) {\n span.setAttribute(key, value);\n }\n\n // Attach caller-provided attributes\n if (attributes) {\n for (const [key, value] of Object.entries(attributes)) {\n span.setAttribute(key, value);\n }\n }\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n });\n}\n"],"mappings":"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","names":["useEffect","useRef","trace","SpanStatusCode","init","metrics","useEffect","trace","SpanStatusCode","useEffect","trace","SpanStatusCode","trace","SpanStatusCode","trace","SpanStatusCode","jsx","useRef","useEffect","tracer","trace","SpanStatusCode","trace","SpanStatusCode","trace","SpanStatusCode"]}
1
+ {"version":3,"sources":["../src/ObservabilityProvider.tsx","../src/lib/otel-init.ts","../src/lib/resource-attributes.ts","../src/lib/fetch-instrumentation.ts","../src/lib/sentry-init.ts","../src/hooks/useWebVitals.ts","../src/hooks/useQueryErrorReporter.ts","../src/ErrorBoundary.tsx","../src/lib/track-event.ts"],"sourcesContent":["import React, { useEffect, useRef, ReactNode } from \"react\";\nimport { trace, SpanStatusCode } from \"@opentelemetry/api\";\nimport {\n initOtelBrowser,\n shutdownOtel,\n type OtelProviders,\n} from \"./lib/otel-init\";\nimport {\n instrumentFetch,\n uninstrumentFetch,\n} from \"./lib/fetch-instrumentation\";\nimport { getDynamicAttributes } from \"./lib/resource-attributes\";\nimport { initSentry, captureError } from \"./lib/sentry-init\";\nimport { useWebVitals } from \"./hooks/useWebVitals\";\nimport { useQueryErrorReporter } from \"./hooks/useQueryErrorReporter\";\nimport { ErrorBoundary } from \"./ErrorBoundary\";\n\nexport interface ObservabilityConfig {\n /** Service name reported to the collector. Falls back to VITE_APP_NAME env var. */\n serviceName?: string;\n /** Service version. Falls back to VITE_APP_VERSION or \"0.0.0\" */\n serviceVersion?: string;\n /** OTel collector base URL. Default: \"https://otel.devfellowship.com\" */\n collectorUrl?: string;\n /** API key sent as x-api-key header. Falls back to VITE_OTEL_API_KEY */\n apiKey?: string;\n /** Sentry DSN. Falls back to VITE_SENTRY_DSN */\n sentryDsn?: string;\n /** Environment label. Default: \"production\" */\n environment?: string;\n /** Master kill-switch. Falls back to VITE_OTEL_ENABLED or import.meta.env.PROD */\n enabled?: boolean;\n /** Capture Core Web Vitals. Default: true */\n enableWebVitals?: boolean;\n /** Report React Query errors as spans. Default: true */\n enableQueryTracking?: boolean;\n /** Auto-trace all fetch() calls. Default: true */\n enableFetchTracing?: boolean;\n}\n\ninterface ObservabilityProviderProps extends ObservabilityConfig {\n children: ReactNode;\n}\n\n// ---- Internal active-instrumentation wrapper ----\n\nfunction ActiveInstrumentation({\n enableWebVitals,\n enableQueryTracking,\n children,\n}: {\n enableWebVitals: boolean;\n enableQueryTracking: boolean;\n children: ReactNode;\n}) {\n if (enableWebVitals) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n useWebVitals();\n }\n\n if (enableQueryTracking) {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n useQueryErrorReporter();\n }\n\n return <>{children}</>;\n}\n\n// ---- Main provider ----\n\n// Safe access to Vite env vars (no-op outside Vite)\nfunction getViteEnv(): Record<string, string | boolean | undefined> {\n try {\n return (import.meta as ImportMeta & { env: Record<string, string | boolean> }).env ?? {};\n } catch {\n return {};\n }\n}\n\nexport function ObservabilityProvider({\n serviceName,\n serviceVersion,\n collectorUrl,\n apiKey,\n sentryDsn,\n environment,\n enabled,\n enableWebVitals = true,\n enableQueryTracking = true,\n enableFetchTracing = true,\n children,\n}: ObservabilityProviderProps) {\n const providersRef = useRef<OtelProviders | null>(null);\n\n const env = getViteEnv();\n const resolvedServiceName = serviceName ?? (env.VITE_APP_NAME as string) ?? \"unknown\";\n const resolvedServiceVersion = serviceVersion ?? (env.VITE_APP_VERSION as string) ?? \"0.0.0\";\n const resolvedCollectorUrl = collectorUrl ?? \"https://otel.devfellowship.com\";\n const resolvedApiKey = apiKey ?? (env.VITE_OTEL_API_KEY as string);\n const resolvedSentryDsn = sentryDsn ?? (env.VITE_SENTRY_DSN as string);\n const sandboxSlug = env.VITE_SANDBOX_SLUG as string | undefined;\n const sandboxBranch = env.VITE_SANDBOX_BRANCH as string | undefined;\n const resolvedEnvironment = environment ?? (sandboxSlug ? \"sandbox\" : (env.PROD ? \"production\" : \"development\"));\n const isEnabled = enabled ?? (env.VITE_OTEL_ENABLED === \"true\" || env.PROD === true);\n\n useEffect(() => {\n if (!isEnabled) return;\n\n // Initialize Sentry (if DSN provided)\n if (resolvedSentryDsn) {\n initSentry({\n dsn: resolvedSentryDsn,\n serviceName: resolvedServiceName,\n serviceVersion: resolvedServiceVersion,\n environment: resolvedEnvironment,\n sandboxSlug,\n sandboxBranch,\n });\n }\n\n // Initialize OTel SDK\n const providers = initOtelBrowser({\n serviceName: resolvedServiceName,\n serviceVersion: resolvedServiceVersion,\n collectorUrl: resolvedCollectorUrl,\n apiKey: resolvedApiKey,\n sandboxSlug,\n sandboxBranch,\n });\n providersRef.current = providers;\n\n // Fetch instrumentation\n if (enableFetchTracing) {\n const tracer = trace.getTracer(\"@dfl/observability\");\n instrumentFetch(tracer);\n }\n\n // Global error handlers\n const tracer = trace.getTracer(\"@dfl/observability\");\n\n const handleWindowError = (event: ErrorEvent) => {\n tracer.startActiveSpan(\"window.onerror\", (span) => {\n const dynamicAttrs = getDynamicAttributes();\n for (const [key, value] of Object.entries(dynamicAttrs)) {\n span.setAttribute(key, value);\n }\n\n span.setAttribute(\"error.message\", event.message);\n span.setAttribute(\"error.source\", event.filename ?? \"\");\n span.setAttribute(\"error.lineno\", event.lineno ?? 0);\n span.setAttribute(\"error.colno\", event.colno ?? 0);\n\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: event.message,\n });\n\n if (event.error instanceof Error) {\n span.recordException(event.error);\n captureError(event.error, {\n source: event.filename ?? \"\",\n lineno: String(event.lineno ?? 0),\n });\n }\n\n span.end();\n });\n };\n\n const handleUnhandledRejection = (event: PromiseRejectionEvent) => {\n tracer.startActiveSpan(\"unhandled-rejection\", (span) => {\n const dynamicAttrs = getDynamicAttributes();\n for (const [key, value] of Object.entries(dynamicAttrs)) {\n span.setAttribute(key, value);\n }\n\n const reason = event.reason;\n const message =\n reason instanceof Error ? reason.message : String(reason);\n\n span.setAttribute(\"error.message\", message);\n span.setAttribute(\"error.type\", \"unhandled_promise_rejection\");\n\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n\n if (reason instanceof Error) {\n span.recordException(reason);\n captureError(reason, { type: \"unhandled_promise_rejection\" });\n }\n\n span.end();\n });\n };\n\n window.addEventListener(\"error\", handleWindowError);\n window.addEventListener(\"unhandledrejection\", handleUnhandledRejection);\n\n return () => {\n window.removeEventListener(\"error\", handleWindowError);\n window.removeEventListener(\"unhandledrejection\", handleUnhandledRejection);\n\n if (enableFetchTracing) {\n uninstrumentFetch();\n }\n\n if (providersRef.current) {\n shutdownOtel(providersRef.current);\n providersRef.current = null;\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isEnabled]);\n\n // No-op mode: just render children without any instrumentation\n if (!isEnabled) {\n return <>{children}</>;\n }\n\n return (\n <ErrorBoundary>\n <ActiveInstrumentation\n enableWebVitals={enableWebVitals}\n enableQueryTracking={enableQueryTracking}\n >\n {children}\n </ActiveInstrumentation>\n </ErrorBoundary>\n );\n}\n","import { resourceFromAttributes } from \"@opentelemetry/resources\";\nimport {\n WebTracerProvider,\n BatchSpanProcessor,\n} from \"@opentelemetry/sdk-trace-web\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-http\";\nimport {\n MeterProvider,\n PeriodicExportingMetricReader,\n} from \"@opentelemetry/sdk-metrics\";\nimport { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-http\";\nimport { trace, metrics } from \"@opentelemetry/api\";\nimport { getStaticAttributes } from \"./resource-attributes\";\n\nexport interface OtelBrowserConfig {\n serviceName: string;\n serviceVersion: string;\n collectorUrl: string;\n apiKey?: string;\n sandboxSlug?: string;\n sandboxBranch?: string;\n}\n\nexport interface OtelProviders {\n tracerProvider: WebTracerProvider;\n meterProvider: MeterProvider;\n}\n\nexport function initOtelBrowser(config: OtelBrowserConfig): OtelProviders {\n const { serviceName, serviceVersion, collectorUrl, apiKey, sandboxSlug, sandboxBranch } = config;\n\n const headers: Record<string, string> = {};\n if (apiKey) {\n headers[\"x-api-key\"] = apiKey;\n }\n\n const staticAttributes = getStaticAttributes();\n\n const resource = resourceFromAttributes({\n \"service.name\": serviceName,\n \"service.version\": serviceVersion,\n ...staticAttributes,\n ...(sandboxSlug ? { \"sandbox.slug\": sandboxSlug } : {}),\n ...(sandboxBranch ? { \"sandbox.branch\": sandboxBranch } : {}),\n });\n\n // --- Traces ---\n const traceExporter = new OTLPTraceExporter({\n url: `${collectorUrl}/v1/traces`,\n headers,\n });\n\n const tracerProvider = new WebTracerProvider({\n resource,\n spanProcessors: [new BatchSpanProcessor(traceExporter)],\n });\n\n tracerProvider.register();\n\n // --- Metrics ---\n const metricExporter = new OTLPMetricExporter({\n url: `${collectorUrl}/v1/metrics`,\n headers,\n });\n\n const metricReader = new PeriodicExportingMetricReader({\n exporter: metricExporter,\n exportIntervalMillis: 60_000,\n });\n\n const meterProvider = new MeterProvider({\n resource,\n readers: [metricReader],\n });\n\n metrics.setGlobalMeterProvider(meterProvider);\n\n // Store the collector URL globally so the fetch instrumentation can filter it\n (globalThis as Record<string, unknown>).__otelCollectorUrl = collectorUrl;\n\n return { tracerProvider, meterProvider };\n}\n\nexport function shutdownOtel(providers: OtelProviders): void {\n const { tracerProvider, meterProvider } = providers;\n\n trace.disable();\n metrics.disable();\n\n tracerProvider.shutdown().catch((err) => {\n console.warn(\"[observability] tracer shutdown error:\", err);\n });\n\n meterProvider.shutdown().catch((err) => {\n console.warn(\"[observability] meter shutdown error:\", err);\n });\n}\n","/**\n * Auto-collected resource attributes for browser observability.\n *\n * Static attributes are gathered once at init time.\n * Dynamic attributes change during the session (e.g. route, viewport).\n * User attributes are set after authentication completes.\n */\n\n// ---- Module-level mutable state for post-auth enrichment ----\nlet _userId: string | undefined;\nlet _userRole: string | undefined;\n\nexport function setUserId(id: string): void {\n _userId = id;\n}\n\nexport function setUserRole(role: string): void {\n _userRole = role;\n}\n\n// ---- Session ID (stable per tab session) ----\nfunction getSessionId(): string {\n const key = \"__otel_session_id\";\n let id = sessionStorage.getItem(key);\n if (!id) {\n id = crypto.randomUUID();\n sessionStorage.setItem(key, id);\n }\n return id;\n}\n\n// ---- Browser detection (lightweight, no external lib) ----\ninterface BrowserInfo {\n name: string;\n version: string;\n}\n\nfunction detectBrowser(): BrowserInfo {\n const ua = navigator.userAgent;\n\n if (ua.includes(\"Firefox/\")) {\n const match = ua.match(/Firefox\\/([\\d.]+)/);\n return { name: \"Firefox\", version: match?.[1] ?? \"unknown\" };\n }\n if (ua.includes(\"Edg/\")) {\n const match = ua.match(/Edg\\/([\\d.]+)/);\n return { name: \"Edge\", version: match?.[1] ?? \"unknown\" };\n }\n if (ua.includes(\"Chrome/\") && !ua.includes(\"Edg/\")) {\n const match = ua.match(/Chrome\\/([\\d.]+)/);\n return { name: \"Chrome\", version: match?.[1] ?? \"unknown\" };\n }\n if (ua.includes(\"Safari/\") && !ua.includes(\"Chrome/\")) {\n const match = ua.match(/Version\\/([\\d.]+)/);\n return { name: \"Safari\", version: match?.[1] ?? \"unknown\" };\n }\n\n return { name: \"unknown\", version: \"unknown\" };\n}\n\n// ---- OS detection ----\nfunction detectOS(): string {\n const ua = navigator.userAgent;\n if (ua.includes(\"Win\")) return \"Windows\";\n if (ua.includes(\"Mac\")) return \"macOS\";\n if (ua.includes(\"Linux\")) return \"Linux\";\n if (ua.includes(\"Android\")) return \"Android\";\n if (/iPhone|iPad|iPod/.test(ua)) return \"iOS\";\n return \"unknown\";\n}\n\n// ---- Device type via viewport heuristic ----\nfunction detectDeviceType(): string {\n const width = window.innerWidth;\n if (width < 768) return \"mobile\";\n if (width < 1024) return \"tablet\";\n return \"desktop\";\n}\n\n// ---- Connection info ----\nfunction getEffectiveConnectionType(): string | undefined {\n const nav = navigator as Navigator & {\n connection?: { effectiveType?: string };\n };\n return nav.connection?.effectiveType;\n}\n\n// ---- Public API ----\n\n/**\n * Attributes that are stable for the lifetime of the page.\n * Collected once during OTel SDK initialization.\n */\nexport function getStaticAttributes(): Record<string, string> {\n const browser = detectBrowser();\n const attrs: Record<string, string> = {\n \"browser.name\": browser.name,\n \"browser.version\": browser.version,\n \"os.type\": detectOS(),\n \"device.type\": detectDeviceType(),\n \"session.id\": getSessionId(),\n };\n\n return attrs;\n}\n\n/**\n * Attributes that may change between spans (viewport, route, user, connection).\n * Called per-span or at strategic points.\n */\nexport function getDynamicAttributes(): Record<string, string | number> {\n const attrs: Record<string, string | number> = {\n \"viewport.width\": window.innerWidth,\n \"viewport.height\": window.innerHeight,\n };\n\n if (_userId) attrs[\"user.id\"] = _userId;\n if (_userRole) attrs[\"user.role\"] = _userRole;\n\n const connType = getEffectiveConnectionType();\n if (connType) attrs[\"connection.effectiveType\"] = connType;\n\n // page.route is typically set by the ObservabilityProvider via React Router\n const route = (globalThis as Record<string, unknown>).__otelCurrentRoute;\n if (typeof route === \"string\") attrs[\"page.route\"] = route;\n\n return attrs;\n}\n","import { Tracer, SpanKind, SpanStatusCode } from \"@opentelemetry/api\";\nimport { getDynamicAttributes } from \"./resource-attributes\";\n\ntype NativeFetch = typeof globalThis.fetch;\n\nlet _originalFetch: NativeFetch | null = null;\n\n/**\n * Monkey-patches `window.fetch` to automatically create OTel spans for\n * every outgoing HTTP request.\n *\n * Collector URLs are filtered out to prevent infinite export loops.\n */\nexport function instrumentFetch(tracer: Tracer): void {\n if (_originalFetch) {\n // Already instrumented — avoid double-wrapping.\n return;\n }\n\n _originalFetch = window.fetch.bind(window);\n\n window.fetch = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const url =\n input instanceof Request ? input.url : input instanceof URL ? input.href : input;\n const method = init?.method ?? (input instanceof Request ? input.method : \"GET\");\n\n // Filter out OTel collector traffic to avoid infinite loops.\n const collectorUrl = (globalThis as Record<string, unknown>).__otelCollectorUrl;\n if (typeof collectorUrl === \"string\" && url.startsWith(collectorUrl)) {\n return _originalFetch!(input, init);\n }\n\n const dynamicAttrs = getDynamicAttributes();\n\n return tracer.startActiveSpan(\n `HTTP ${method.toUpperCase()}`,\n {\n kind: SpanKind.CLIENT,\n attributes: {\n \"http.method\": method.toUpperCase(),\n \"http.url\": url,\n ...dynamicAttrs,\n },\n },\n async (span) => {\n try {\n const response = await _originalFetch!(input, init);\n\n span.setAttribute(\"http.status_code\", response.status);\n\n const contentLength = response.headers.get(\"content-length\");\n if (contentLength) {\n span.setAttribute(\n \"http.response_content_length\",\n parseInt(contentLength, 10),\n );\n }\n\n if (response.status >= 400) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: `HTTP ${response.status}`,\n });\n }\n\n return response;\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"Network request failed\";\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n span.recordException(error instanceof Error ? error : new Error(message));\n throw error;\n } finally {\n span.end();\n }\n },\n );\n };\n}\n\n/**\n * Restores the original `window.fetch`, removing instrumentation.\n */\nexport function uninstrumentFetch(): void {\n if (_originalFetch) {\n window.fetch = _originalFetch;\n _originalFetch = null;\n }\n}\n","import * as Sentry from \"@sentry/react\";\n\nexport interface SentryConfig {\n dsn: string;\n serviceName: string;\n serviceVersion: string;\n environment: string;\n sandboxSlug?: string;\n sandboxBranch?: string;\n}\n\nlet _initialized = false;\n\nexport function initSentry(config: SentryConfig): void {\n if (_initialized) return;\n\n Sentry.init({\n dsn: config.dsn,\n environment: config.environment,\n release: `${config.serviceName}@${config.serviceVersion}`,\n integrations: [\n Sentry.browserTracingIntegration(),\n Sentry.replayIntegration({ maskAllText: false, blockAllMedia: false }),\n ],\n tracesSampleRate: 0.2,\n replaysSessionSampleRate: 0.0,\n replaysOnErrorSampleRate: 1.0,\n beforeSend(event) {\n event.tags = {\n ...event.tags,\n \"service.name\": config.serviceName,\n };\n return event;\n },\n });\n\n Sentry.setTag(\"service.name\", config.serviceName);\n if (config.sandboxSlug) {\n Sentry.setTag(\"sandbox.slug\", config.sandboxSlug);\n }\n if (config.sandboxBranch) {\n Sentry.setTag(\"sandbox.branch\", config.sandboxBranch);\n }\n _initialized = true;\n}\n\nexport function captureError(error: Error, context?: Record<string, string>): void {\n if (!_initialized) return;\n Sentry.withScope((scope) => {\n if (context) {\n for (const [key, value] of Object.entries(context)) {\n scope.setExtra(key, value);\n }\n }\n Sentry.captureException(error);\n });\n}\n\nexport function setSentryUser(id: string, role?: string): void {\n if (!_initialized) return;\n Sentry.setUser({ id, ...(role ? { role } : {}) });\n}\n","import { useEffect, useRef } from \"react\";\nimport { metrics } from \"@opentelemetry/api\";\nimport type { Metric } from \"web-vitals\";\n\n/**\n * Captures Core Web Vitals (LCP, FID, CLS, INP, TTFB) and reports them\n * as OTel histogram metrics via the global MeterProvider.\n *\n * Runs once on mount. The `web-vitals` callbacks fire at most once per\n * metric per page load, so no debouncing is needed.\n */\nexport function useWebVitals(): void {\n const initialized = useRef(false);\n\n useEffect(() => {\n if (initialized.current) return;\n initialized.current = true;\n\n const meter = metrics.getMeter(\"web-vitals\");\n\n const lcpHistogram = meter.createHistogram(\"web_vital.lcp\", {\n description: \"Largest Contentful Paint (ms)\",\n unit: \"ms\",\n });\n const clsHistogram = meter.createHistogram(\"web_vital.cls\", {\n description: \"Cumulative Layout Shift\",\n unit: \"\",\n });\n const inpHistogram = meter.createHistogram(\"web_vital.inp\", {\n description: \"Interaction to Next Paint (ms)\",\n unit: \"ms\",\n });\n const ttfbHistogram = meter.createHistogram(\"web_vital.ttfb\", {\n description: \"Time to First Byte (ms)\",\n unit: \"ms\",\n });\n\n const report = (\n histogram: ReturnType<typeof meter.createHistogram>,\n metric: Metric,\n ) => {\n histogram.record(metric.value, {\n \"metric.id\": metric.id,\n \"metric.rating\": metric.rating,\n });\n };\n\n // Dynamic import so tree-shaking can drop web-vitals entirely when unused.\n import(\"web-vitals\").then(({ onLCP, onCLS, onINP, onTTFB }) => {\n onLCP((m) => report(lcpHistogram, m));\n onCLS((m) => report(clsHistogram, m));\n onINP((m) => report(inpHistogram, m));\n onTTFB((m) => report(ttfbHistogram, m));\n });\n }, []);\n}\n","import { useEffect } from \"react\";\nimport { useQueryClient, type QueryClient } from \"@tanstack/react-query\";\nimport { trace, SpanStatusCode } from \"@opentelemetry/api\";\nimport { getDynamicAttributes } from \"../lib/resource-attributes\";\n\n/**\n * Subscribes to the React Query cache and creates OTel error spans\n * whenever a query transitions to the `error` state.\n *\n * Safe to use outside QueryClientProvider — gracefully no-ops.\n */\nexport function useQueryErrorReporter(): void {\n let queryClient: QueryClient | null = null;\n try {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n queryClient = useQueryClient();\n } catch {\n // Not inside a QueryClientProvider — query error reporting disabled\n }\n\n useEffect(() => {\n if (!queryClient) return;\n const tracer = trace.getTracer(\"@dfl/observability\");\n\n const unsubscribe = queryClient.getQueryCache().subscribe((event) => {\n if (event.type === \"updated\" && event.action.type === \"error\") {\n const query = event.query;\n const error = query.state.error;\n\n tracer.startActiveSpan(\"react-query-error\", (span) => {\n const dynamicAttrs = getDynamicAttributes();\n for (const [key, value] of Object.entries(dynamicAttrs)) {\n span.setAttribute(key, value);\n }\n\n span.setAttribute(\"query.key\", JSON.stringify(query.queryKey));\n span.setAttribute(\n \"error.message\",\n error instanceof Error ? error.message : String(error),\n );\n span.setAttribute(\n \"error.type\",\n error instanceof Error ? error.constructor.name : typeof error,\n );\n\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message:\n error instanceof Error ? error.message : \"React Query error\",\n });\n\n if (error instanceof Error) {\n span.recordException(error);\n }\n\n span.end();\n });\n }\n });\n\n return () => {\n unsubscribe();\n };\n }, [queryClient]);\n}\n","import React, { Component, ErrorInfo, ReactNode } from \"react\";\nimport { trace, SpanStatusCode } from \"@opentelemetry/api\";\nimport { captureError } from \"./lib/sentry-init\";\n\ninterface ErrorBoundaryProps {\n children: ReactNode;\n fallback?: ReactNode;\n}\n\ninterface ErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * React error boundary that reports uncaught component errors to OTel\n * as error spans before rendering a fallback UI.\n */\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n constructor(props: ErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): ErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: ErrorInfo): void {\n const tracer = trace.getTracer(\"@dfl/observability\");\n\n tracer.startActiveSpan(\"react-error-boundary\", (span) => {\n span.setAttribute(\"error.message\", error.message);\n span.setAttribute(\"error.stack\", error.stack ?? \"\");\n span.setAttribute(\n \"error.component_stack\",\n errorInfo.componentStack ?? \"\",\n );\n\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n\n span.recordException(error);\n span.end();\n });\n\n captureError(error, {\n component_stack: errorInfo.componentStack ?? \"\",\n });\n }\n\n private handleReload = (): void => {\n window.location.reload();\n };\n\n render(): ReactNode {\n if (this.state.hasError) {\n if (this.props.fallback) {\n return this.props.fallback;\n }\n\n return (\n <div className=\"flex min-h-[200px] flex-col items-center justify-center gap-4 rounded-lg border border-red-200 bg-red-50 p-8 text-center dark:border-red-900 dark:bg-red-950\">\n <h2 className=\"text-lg font-semibold text-red-800 dark:text-red-200\">\n Something went wrong\n </h2>\n <p className=\"max-w-md text-sm text-red-600 dark:text-red-400\">\n An unexpected error occurred. Please try reloading the page.\n </p>\n <button\n onClick={this.handleReload}\n className=\"rounded-md bg-red-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:bg-red-700 dark:hover:bg-red-600\"\n >\n Reload page\n </button>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n","import { trace, SpanStatusCode } from \"@opentelemetry/api\";\nimport { getDynamicAttributes } from \"./resource-attributes\";\n\n/**\n * Emit a custom observability event as an OTel span.\n *\n * Uses the global tracer registered by the ObservabilityProvider,\n * so it can be called from anywhere in the app without prop-drilling.\n *\n * @example\n * ```ts\n * trackEvent(\"button_click\", { \"button.id\": \"checkout\", \"page\": \"/cart\" });\n * ```\n */\nexport function trackEvent(\n name: string,\n attributes?: Record<string, string | number | boolean>,\n): void {\n const tracer = trace.getTracer(\"@dfl/observability\");\n\n tracer.startActiveSpan(name, (span) => {\n const dynamicAttrs = getDynamicAttributes();\n\n span.setAttribute(\"event.name\", name);\n\n // Attach dynamic resource attributes (viewport, user, route, etc.)\n for (const [key, value] of Object.entries(dynamicAttrs)) {\n span.setAttribute(key, value);\n }\n\n // Attach caller-provided attributes\n if (attributes) {\n for (const [key, value] of Object.entries(attributes)) {\n span.setAttribute(key, value);\n }\n }\n\n span.setStatus({ code: SpanStatusCode.OK });\n span.end();\n });\n}\n"],"mappings":";AAAA,SAAgB,aAAAA,YAAW,UAAAC,eAAyB;AACpD,SAAS,SAAAC,QAAO,kBAAAC,uBAAsB;;;ACDtC,SAAS,8BAA8B;AACvC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,0BAA0B;AACnC,SAAS,OAAO,eAAe;;;ACF/B,IAAI;AACJ,IAAI;AAEG,SAAS,UAAU,IAAkB;AAC1C,YAAU;AACZ;AAEO,SAAS,YAAY,MAAoB;AAC9C,cAAY;AACd;AAGA,SAAS,eAAuB;AAC9B,QAAM,MAAM;AACZ,MAAI,KAAK,eAAe,QAAQ,GAAG;AACnC,MAAI,CAAC,IAAI;AACP,SAAK,OAAO,WAAW;AACvB,mBAAe,QAAQ,KAAK,EAAE;AAAA,EAChC;AACA,SAAO;AACT;AAQA,SAAS,gBAA6B;AACpC,QAAM,KAAK,UAAU;AAErB,MAAI,GAAG,SAAS,UAAU,GAAG;AAC3B,UAAM,QAAQ,GAAG,MAAM,mBAAmB;AAC1C,WAAO,EAAE,MAAM,WAAW,SAAS,QAAQ,CAAC,KAAK,UAAU;AAAA,EAC7D;AACA,MAAI,GAAG,SAAS,MAAM,GAAG;AACvB,UAAM,QAAQ,GAAG,MAAM,eAAe;AACtC,WAAO,EAAE,MAAM,QAAQ,SAAS,QAAQ,CAAC,KAAK,UAAU;AAAA,EAC1D;AACA,MAAI,GAAG,SAAS,SAAS,KAAK,CAAC,GAAG,SAAS,MAAM,GAAG;AAClD,UAAM,QAAQ,GAAG,MAAM,kBAAkB;AACzC,WAAO,EAAE,MAAM,UAAU,SAAS,QAAQ,CAAC,KAAK,UAAU;AAAA,EAC5D;AACA,MAAI,GAAG,SAAS,SAAS,KAAK,CAAC,GAAG,SAAS,SAAS,GAAG;AACrD,UAAM,QAAQ,GAAG,MAAM,mBAAmB;AAC1C,WAAO,EAAE,MAAM,UAAU,SAAS,QAAQ,CAAC,KAAK,UAAU;AAAA,EAC5D;AAEA,SAAO,EAAE,MAAM,WAAW,SAAS,UAAU;AAC/C;AAGA,SAAS,WAAmB;AAC1B,QAAM,KAAK,UAAU;AACrB,MAAI,GAAG,SAAS,KAAK,EAAG,QAAO;AAC/B,MAAI,GAAG,SAAS,KAAK,EAAG,QAAO;AAC/B,MAAI,GAAG,SAAS,OAAO,EAAG,QAAO;AACjC,MAAI,GAAG,SAAS,SAAS,EAAG,QAAO;AACnC,MAAI,mBAAmB,KAAK,EAAE,EAAG,QAAO;AACxC,SAAO;AACT;AAGA,SAAS,mBAA2B;AAClC,QAAM,QAAQ,OAAO;AACrB,MAAI,QAAQ,IAAK,QAAO;AACxB,MAAI,QAAQ,KAAM,QAAO;AACzB,SAAO;AACT;AAGA,SAAS,6BAAiD;AACxD,QAAM,MAAM;AAGZ,SAAO,IAAI,YAAY;AACzB;AAQO,SAAS,sBAA8C;AAC5D,QAAM,UAAU,cAAc;AAC9B,QAAM,QAAgC;AAAA,IACpC,gBAAgB,QAAQ;AAAA,IACxB,mBAAmB,QAAQ;AAAA,IAC3B,WAAW,SAAS;AAAA,IACpB,eAAe,iBAAiB;AAAA,IAChC,cAAc,aAAa;AAAA,EAC7B;AAEA,SAAO;AACT;AAMO,SAAS,uBAAwD;AACtE,QAAM,QAAyC;AAAA,IAC7C,kBAAkB,OAAO;AAAA,IACzB,mBAAmB,OAAO;AAAA,EAC5B;AAEA,MAAI,QAAS,OAAM,SAAS,IAAI;AAChC,MAAI,UAAW,OAAM,WAAW,IAAI;AAEpC,QAAM,WAAW,2BAA2B;AAC5C,MAAI,SAAU,OAAM,0BAA0B,IAAI;AAGlD,QAAM,QAAS,WAAuC;AACtD,MAAI,OAAO,UAAU,SAAU,OAAM,YAAY,IAAI;AAErD,SAAO;AACT;;;ADnGO,SAAS,gBAAgB,QAA0C;AACxE,QAAM,EAAE,aAAa,gBAAgB,cAAc,QAAQ,aAAa,cAAc,IAAI;AAE1F,QAAM,UAAkC,CAAC;AACzC,MAAI,QAAQ;AACV,YAAQ,WAAW,IAAI;AAAA,EACzB;AAEA,QAAM,mBAAmB,oBAAoB;AAE7C,QAAM,WAAW,uBAAuB;AAAA,IACtC,gBAAgB;AAAA,IAChB,mBAAmB;AAAA,IACnB,GAAG;AAAA,IACH,GAAI,cAAc,EAAE,gBAAgB,YAAY,IAAI,CAAC;AAAA,IACrD,GAAI,gBAAgB,EAAE,kBAAkB,cAAc,IAAI,CAAC;AAAA,EAC7D,CAAC;AAGD,QAAM,gBAAgB,IAAI,kBAAkB;AAAA,IAC1C,KAAK,GAAG,YAAY;AAAA,IACpB;AAAA,EACF,CAAC;AAED,QAAM,iBAAiB,IAAI,kBAAkB;AAAA,IAC3C;AAAA,IACA,gBAAgB,CAAC,IAAI,mBAAmB,aAAa,CAAC;AAAA,EACxD,CAAC;AAED,iBAAe,SAAS;AAGxB,QAAM,iBAAiB,IAAI,mBAAmB;AAAA,IAC5C,KAAK,GAAG,YAAY;AAAA,IACpB;AAAA,EACF,CAAC;AAED,QAAM,eAAe,IAAI,8BAA8B;AAAA,IACrD,UAAU;AAAA,IACV,sBAAsB;AAAA,EACxB,CAAC;AAED,QAAM,gBAAgB,IAAI,cAAc;AAAA,IACtC;AAAA,IACA,SAAS,CAAC,YAAY;AAAA,EACxB,CAAC;AAED,UAAQ,uBAAuB,aAAa;AAG5C,EAAC,WAAuC,qBAAqB;AAE7D,SAAO,EAAE,gBAAgB,cAAc;AACzC;AAEO,SAAS,aAAa,WAAgC;AAC3D,QAAM,EAAE,gBAAgB,cAAc,IAAI;AAE1C,QAAM,QAAQ;AACd,UAAQ,QAAQ;AAEhB,iBAAe,SAAS,EAAE,MAAM,CAAC,QAAQ;AACvC,YAAQ,KAAK,0CAA0C,GAAG;AAAA,EAC5D,CAAC;AAED,gBAAc,SAAS,EAAE,MAAM,CAAC,QAAQ;AACtC,YAAQ,KAAK,yCAAyC,GAAG;AAAA,EAC3D,CAAC;AACH;;;AEhGA,SAAiB,UAAU,sBAAsB;AAKjD,IAAI,iBAAqC;AAQlC,SAAS,gBAAgB,QAAsB;AACpD,MAAI,gBAAgB;AAElB;AAAA,EACF;AAEA,mBAAiB,OAAO,MAAM,KAAK,MAAM;AAEzC,SAAO,QAAQ,OACb,OACAC,UACsB;AACtB,UAAM,MACJ,iBAAiB,UAAU,MAAM,MAAM,iBAAiB,MAAM,MAAM,OAAO;AAC7E,UAAM,SAASA,OAAM,WAAW,iBAAiB,UAAU,MAAM,SAAS;AAG1E,UAAM,eAAgB,WAAuC;AAC7D,QAAI,OAAO,iBAAiB,YAAY,IAAI,WAAW,YAAY,GAAG;AACpE,aAAO,eAAgB,OAAOA,KAAI;AAAA,IACpC;AAEA,UAAM,eAAe,qBAAqB;AAE1C,WAAO,OAAO;AAAA,MACZ,QAAQ,OAAO,YAAY,CAAC;AAAA,MAC5B;AAAA,QACE,MAAM,SAAS;AAAA,QACf,YAAY;AAAA,UACV,eAAe,OAAO,YAAY;AAAA,UAClC,YAAY;AAAA,UACZ,GAAG;AAAA,QACL;AAAA,MACF;AAAA,MACA,OAAO,SAAS;AACd,YAAI;AACF,gBAAM,WAAW,MAAM,eAAgB,OAAOA,KAAI;AAElD,eAAK,aAAa,oBAAoB,SAAS,MAAM;AAErD,gBAAM,gBAAgB,SAAS,QAAQ,IAAI,gBAAgB;AAC3D,cAAI,eAAe;AACjB,iBAAK;AAAA,cACH;AAAA,cACA,SAAS,eAAe,EAAE;AAAA,YAC5B;AAAA,UACF;AAEA,cAAI,SAAS,UAAU,KAAK;AAC1B,iBAAK,UAAU;AAAA,cACb,MAAM,eAAe;AAAA,cACrB,SAAS,QAAQ,SAAS,MAAM;AAAA,YAClC,CAAC;AAAA,UACH;AAEA,iBAAO;AAAA,QACT,SAAS,OAAO;AACd,gBAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU;AAC3C,eAAK,UAAU,EAAE,MAAM,eAAe,OAAO,QAAQ,CAAC;AACtD,eAAK,gBAAgB,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,CAAC;AACxE,gBAAM;AAAA,QACR,UAAE;AACA,eAAK,IAAI;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAKO,SAAS,oBAA0B;AACxC,MAAI,gBAAgB;AAClB,WAAO,QAAQ;AACf,qBAAiB;AAAA,EACnB;AACF;;;AC3FA,YAAY,YAAY;AAWxB,IAAI,eAAe;AAEZ,SAAS,WAAW,QAA4B;AACrD,MAAI,aAAc;AAElB,EAAO,YAAK;AAAA,IACV,KAAK,OAAO;AAAA,IACZ,aAAa,OAAO;AAAA,IACpB,SAAS,GAAG,OAAO,WAAW,IAAI,OAAO,cAAc;AAAA,IACvD,cAAc;AAAA,MACL,iCAA0B;AAAA,MAC1B,yBAAkB,EAAE,aAAa,OAAO,eAAe,MAAM,CAAC;AAAA,IACvE;AAAA,IACA,kBAAkB;AAAA,IAClB,0BAA0B;AAAA,IAC1B,0BAA0B;AAAA,IAC1B,WAAW,OAAO;AAChB,YAAM,OAAO;AAAA,QACX,GAAG,MAAM;AAAA,QACT,gBAAgB,OAAO;AAAA,MACzB;AACA,aAAO;AAAA,IACT;AAAA,EACF,CAAC;AAED,EAAO,cAAO,gBAAgB,OAAO,WAAW;AAChD,MAAI,OAAO,aAAa;AACtB,IAAO,cAAO,gBAAgB,OAAO,WAAW;AAAA,EAClD;AACA,MAAI,OAAO,eAAe;AACxB,IAAO,cAAO,kBAAkB,OAAO,aAAa;AAAA,EACtD;AACA,iBAAe;AACjB;AAEO,SAAS,aAAa,OAAc,SAAwC;AACjF,MAAI,CAAC,aAAc;AACnB,EAAO,iBAAU,CAAC,UAAU;AAC1B,QAAI,SAAS;AACX,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,cAAM,SAAS,KAAK,KAAK;AAAA,MAC3B;AAAA,IACF;AACA,IAAO,wBAAiB,KAAK;AAAA,EAC/B,CAAC;AACH;AAEO,SAAS,cAAc,IAAY,MAAqB;AAC7D,MAAI,CAAC,aAAc;AACnB,EAAO,eAAQ,EAAE,IAAI,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC,EAAG,CAAC;AAClD;;;AC7DA,SAAS,WAAW,cAAc;AAClC,SAAS,WAAAC,gBAAe;AAUjB,SAAS,eAAqB;AACnC,QAAM,cAAc,OAAO,KAAK;AAEhC,YAAU,MAAM;AACd,QAAI,YAAY,QAAS;AACzB,gBAAY,UAAU;AAEtB,UAAM,QAAQA,SAAQ,SAAS,YAAY;AAE3C,UAAM,eAAe,MAAM,gBAAgB,iBAAiB;AAAA,MAC1D,aAAa;AAAA,MACb,MAAM;AAAA,IACR,CAAC;AACD,UAAM,eAAe,MAAM,gBAAgB,iBAAiB;AAAA,MAC1D,aAAa;AAAA,MACb,MAAM;AAAA,IACR,CAAC;AACD,UAAM,eAAe,MAAM,gBAAgB,iBAAiB;AAAA,MAC1D,aAAa;AAAA,MACb,MAAM;AAAA,IACR,CAAC;AACD,UAAM,gBAAgB,MAAM,gBAAgB,kBAAkB;AAAA,MAC5D,aAAa;AAAA,MACb,MAAM;AAAA,IACR,CAAC;AAED,UAAM,SAAS,CACb,WACA,WACG;AACH,gBAAU,OAAO,OAAO,OAAO;AAAA,QAC7B,aAAa,OAAO;AAAA,QACpB,iBAAiB,OAAO;AAAA,MAC1B,CAAC;AAAA,IACH;AAGA,WAAO,YAAY,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,OAAO,OAAO,MAAM;AAC7D,YAAM,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AACpC,YAAM,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AACpC,YAAM,CAAC,MAAM,OAAO,cAAc,CAAC,CAAC;AACpC,aAAO,CAAC,MAAM,OAAO,eAAe,CAAC,CAAC;AAAA,IACxC,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AACP;;;ACvDA,SAAS,aAAAC,kBAAiB;AAC1B,SAAS,sBAAwC;AACjD,SAAS,SAAAC,QAAO,kBAAAC,uBAAsB;AAS/B,SAAS,wBAA8B;AAC5C,MAAI,cAAkC;AACtC,MAAI;AAEF,kBAAc,eAAe;AAAA,EAC/B,QAAQ;AAAA,EAER;AAEA,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,YAAa;AAClB,UAAM,SAASC,OAAM,UAAU,oBAAoB;AAEnD,UAAM,cAAc,YAAY,cAAc,EAAE,UAAU,CAAC,UAAU;AACnE,UAAI,MAAM,SAAS,aAAa,MAAM,OAAO,SAAS,SAAS;AAC7D,cAAM,QAAQ,MAAM;AACpB,cAAM,QAAQ,MAAM,MAAM;AAE1B,eAAO,gBAAgB,qBAAqB,CAAC,SAAS;AACpD,gBAAM,eAAe,qBAAqB;AAC1C,qBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,GAAG;AACvD,iBAAK,aAAa,KAAK,KAAK;AAAA,UAC9B;AAEA,eAAK,aAAa,aAAa,KAAK,UAAU,MAAM,QAAQ,CAAC;AAC7D,eAAK;AAAA,YACH;AAAA,YACA,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,UACvD;AACA,eAAK;AAAA,YACH;AAAA,YACA,iBAAiB,QAAQ,MAAM,YAAY,OAAO,OAAO;AAAA,UAC3D;AAEA,eAAK,UAAU;AAAA,YACb,MAAMC,gBAAe;AAAA,YACrB,SACE,iBAAiB,QAAQ,MAAM,UAAU;AAAA,UAC7C,CAAC;AAED,cAAI,iBAAiB,OAAO;AAC1B,iBAAK,gBAAgB,KAAK;AAAA,UAC5B;AAEA,eAAK,IAAI;AAAA,QACX,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAED,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,WAAW,CAAC;AAClB;;;AChEA,SAAgB,iBAAuC;AACvD,SAAS,SAAAC,QAAO,kBAAAC,uBAAsB;AAkE9B,SACE,KADF;AAjDD,IAAM,gBAAN,cAA4B,UAGjC;AAAA,EACA,YAAY,OAA2B;AACrC,UAAM,KAAK;AAiCb,SAAQ,eAAe,MAAY;AACjC,aAAO,SAAS,OAAO;AAAA,IACzB;AAlCE,SAAK,QAAQ,EAAE,UAAU,OAAO,OAAO,KAAK;AAAA,EAC9C;AAAA,EAEA,OAAO,yBAAyB,OAAkC;AAChE,WAAO,EAAE,UAAU,MAAM,MAAM;AAAA,EACjC;AAAA,EAEA,kBAAkB,OAAc,WAA4B;AAC1D,UAAM,SAASC,OAAM,UAAU,oBAAoB;AAEnD,WAAO,gBAAgB,wBAAwB,CAAC,SAAS;AACvD,WAAK,aAAa,iBAAiB,MAAM,OAAO;AAChD,WAAK,aAAa,eAAe,MAAM,SAAS,EAAE;AAClD,WAAK;AAAA,QACH;AAAA,QACA,UAAU,kBAAkB;AAAA,MAC9B;AAEA,WAAK,UAAU;AAAA,QACb,MAAMC,gBAAe;AAAA,QACrB,SAAS,MAAM;AAAA,MACjB,CAAC;AAED,WAAK,gBAAgB,KAAK;AAC1B,WAAK,IAAI;AAAA,IACX,CAAC;AAED,iBAAa,OAAO;AAAA,MAClB,iBAAiB,UAAU,kBAAkB;AAAA,IAC/C,CAAC;AAAA,EACH;AAAA,EAMA,SAAoB;AAClB,QAAI,KAAK,MAAM,UAAU;AACvB,UAAI,KAAK,MAAM,UAAU;AACvB,eAAO,KAAK,MAAM;AAAA,MACpB;AAEA,aACE,qBAAC,SAAI,WAAU,gKACb;AAAA,4BAAC,QAAG,WAAU,wDAAuD,kCAErE;AAAA,QACA,oBAAC,OAAE,WAAU,mDAAkD,0EAE/D;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,KAAK;AAAA,YACd,WAAU;AAAA,YACX;AAAA;AAAA,QAED;AAAA,SACF;AAAA,IAEJ;AAEA,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;;;APrBS,0BAAAC,YAAA;AAnBT,SAAS,sBAAsB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,MAAI,iBAAiB;AAEnB,iBAAa;AAAA,EACf;AAEA,MAAI,qBAAqB;AAEvB,0BAAsB;AAAA,EACxB;AAEA,SAAO,gBAAAA,KAAA,YAAG,UAAS;AACrB;AAKA,SAAS,aAA2D;AAClE,MAAI;AACF,WAAQ,YAAuE,OAAO,CAAC;AAAA,EACzF,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,kBAAkB;AAAA,EAClB,sBAAsB;AAAA,EACtB,qBAAqB;AAAA,EACrB;AACF,GAA+B;AAC7B,QAAM,eAAeC,QAA6B,IAAI;AAEtD,QAAM,MAAM,WAAW;AACvB,QAAM,sBAAsB,eAAgB,IAAI,iBAA4B;AAC5E,QAAM,yBAAyB,kBAAmB,IAAI,oBAA+B;AACrF,QAAM,uBAAuB,gBAAgB;AAC7C,QAAM,iBAAiB,UAAW,IAAI;AACtC,QAAM,oBAAoB,aAAc,IAAI;AAC5C,QAAM,cAAc,IAAI;AACxB,QAAM,gBAAgB,IAAI;AAC1B,QAAM,sBAAsB,gBAAgB,cAAc,YAAa,IAAI,OAAO,eAAe;AACjG,QAAM,YAAY,YAAY,IAAI,sBAAsB,UAAU,IAAI,SAAS;AAE/E,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,UAAW;AAGhB,QAAI,mBAAmB;AACrB,iBAAW;AAAA,QACT,KAAK;AAAA,QACL,aAAa;AAAA,QACb,gBAAgB;AAAA,QAChB,aAAa;AAAA,QACb;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAGA,UAAM,YAAY,gBAAgB;AAAA,MAChC,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,cAAc;AAAA,MACd,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,IACF,CAAC;AACD,iBAAa,UAAU;AAGvB,QAAI,oBAAoB;AACtB,YAAMC,UAASC,OAAM,UAAU,oBAAoB;AACnD,sBAAgBD,OAAM;AAAA,IACxB;AAGA,UAAM,SAASC,OAAM,UAAU,oBAAoB;AAEnD,UAAM,oBAAoB,CAAC,UAAsB;AAC/C,aAAO,gBAAgB,kBAAkB,CAAC,SAAS;AACjD,cAAM,eAAe,qBAAqB;AAC1C,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,GAAG;AACvD,eAAK,aAAa,KAAK,KAAK;AAAA,QAC9B;AAEA,aAAK,aAAa,iBAAiB,MAAM,OAAO;AAChD,aAAK,aAAa,gBAAgB,MAAM,YAAY,EAAE;AACtD,aAAK,aAAa,gBAAgB,MAAM,UAAU,CAAC;AACnD,aAAK,aAAa,eAAe,MAAM,SAAS,CAAC;AAEjD,aAAK,UAAU;AAAA,UACb,MAAMC,gBAAe;AAAA,UACrB,SAAS,MAAM;AAAA,QACjB,CAAC;AAED,YAAI,MAAM,iBAAiB,OAAO;AAChC,eAAK,gBAAgB,MAAM,KAAK;AAChC,uBAAa,MAAM,OAAO;AAAA,YACxB,QAAQ,MAAM,YAAY;AAAA,YAC1B,QAAQ,OAAO,MAAM,UAAU,CAAC;AAAA,UAClC,CAAC;AAAA,QACH;AAEA,aAAK,IAAI;AAAA,MACX,CAAC;AAAA,IACH;AAEA,UAAM,2BAA2B,CAAC,UAAiC;AACjE,aAAO,gBAAgB,uBAAuB,CAAC,SAAS;AACtD,cAAM,eAAe,qBAAqB;AAC1C,mBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,GAAG;AACvD,eAAK,aAAa,KAAK,KAAK;AAAA,QAC9B;AAEA,cAAM,SAAS,MAAM;AACrB,cAAM,UACJ,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AAE1D,aAAK,aAAa,iBAAiB,OAAO;AAC1C,aAAK,aAAa,cAAc,6BAA6B;AAE7D,aAAK,UAAU,EAAE,MAAMA,gBAAe,OAAO,QAAQ,CAAC;AAEtD,YAAI,kBAAkB,OAAO;AAC3B,eAAK,gBAAgB,MAAM;AAC3B,uBAAa,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAAA,QAC9D;AAEA,aAAK,IAAI;AAAA,MACX,CAAC;AAAA,IACH;AAEA,WAAO,iBAAiB,SAAS,iBAAiB;AAClD,WAAO,iBAAiB,sBAAsB,wBAAwB;AAEtE,WAAO,MAAM;AACX,aAAO,oBAAoB,SAAS,iBAAiB;AACrD,aAAO,oBAAoB,sBAAsB,wBAAwB;AAEzE,UAAI,oBAAoB;AACtB,0BAAkB;AAAA,MACpB;AAEA,UAAI,aAAa,SAAS;AACxB,qBAAa,aAAa,OAAO;AACjC,qBAAa,UAAU;AAAA,MACzB;AAAA,IACF;AAAA,EAEF,GAAG,CAAC,SAAS,CAAC;AAGd,MAAI,CAAC,WAAW;AACd,WAAO,gBAAAL,KAAA,YAAG,UAAS;AAAA,EACrB;AAEA,SACE,gBAAAA,KAAC,iBACC,0BAAAA;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH,GACF;AAEJ;;;AQpOA,SAAS,SAAAM,QAAO,kBAAAC,uBAAsB;AAc/B,SAAS,WACd,MACA,YACM;AACN,QAAM,SAASC,OAAM,UAAU,oBAAoB;AAEnD,SAAO,gBAAgB,MAAM,CAAC,SAAS;AACrC,UAAM,eAAe,qBAAqB;AAE1C,SAAK,aAAa,cAAc,IAAI;AAGpC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,YAAY,GAAG;AACvD,WAAK,aAAa,KAAK,KAAK;AAAA,IAC9B;AAGA,QAAI,YAAY;AACd,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,GAAG;AACrD,aAAK,aAAa,KAAK,KAAK;AAAA,MAC9B;AAAA,IACF;AAEA,SAAK,UAAU,EAAE,MAAMC,gBAAe,GAAG,CAAC;AAC1C,SAAK,IAAI;AAAA,EACX,CAAC;AACH;","names":["useEffect","useRef","trace","SpanStatusCode","init","metrics","useEffect","trace","SpanStatusCode","useEffect","trace","SpanStatusCode","trace","SpanStatusCode","trace","SpanStatusCode","jsx","useRef","useEffect","tracer","trace","SpanStatusCode","trace","SpanStatusCode","trace","SpanStatusCode"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devfellowship/observability",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Browser observability SDK — OpenTelemetry + Sentry for React apps",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,7 +13,8 @@
13
13
  }
14
14
  },
15
15
  "files": [
16
- "dist"
16
+ "dist",
17
+ "README.md"
17
18
  ],
18
19
  "scripts": {
19
20
  "build": "tsup",