@interfere/react 9.0.1 → 10.0.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.
Files changed (187) hide show
  1. package/README.md +4 -4
  2. package/dist/api.d.mts +25 -0
  3. package/dist/api.d.mts.map +1 -0
  4. package/dist/api.mjs +68 -0
  5. package/dist/api.mjs.map +1 -0
  6. package/dist/error-boundary.d.mts +11 -4
  7. package/dist/error-boundary.d.mts.map +1 -1
  8. package/dist/error-boundary.mjs +6 -3
  9. package/dist/error-boundary.mjs.map +1 -1
  10. package/dist/internal/browser-context.d.mts +6 -0
  11. package/dist/internal/browser-context.d.mts.map +1 -0
  12. package/dist/internal/browser-context.mjs +59 -0
  13. package/dist/internal/browser-context.mjs.map +1 -0
  14. package/dist/internal/capture-boundary.d.mts +5 -1
  15. package/dist/internal/capture-boundary.d.mts.map +1 -1
  16. package/dist/internal/capture-boundary.mjs +9 -5
  17. package/dist/internal/capture-boundary.mjs.map +1 -1
  18. package/dist/internal/capture.d.mts +16 -5
  19. package/dist/internal/capture.d.mts.map +1 -1
  20. package/dist/internal/capture.mjs +20 -16
  21. package/dist/internal/capture.mjs.map +1 -1
  22. package/dist/internal/config.d.mts +20 -4
  23. package/dist/internal/config.d.mts.map +1 -1
  24. package/dist/internal/config.mjs +12 -12
  25. package/dist/internal/config.mjs.map +1 -1
  26. package/dist/internal/consent.d.mts.map +1 -1
  27. package/dist/internal/consent.mjs +3 -1
  28. package/dist/internal/consent.mjs.map +1 -1
  29. package/dist/internal/console-patch.d.mts +19 -0
  30. package/dist/internal/console-patch.d.mts.map +1 -0
  31. package/dist/internal/console-patch.mjs +62 -0
  32. package/dist/internal/console-patch.mjs.map +1 -0
  33. package/dist/internal/dom/actionable.d.mts +27 -0
  34. package/dist/internal/dom/actionable.d.mts.map +1 -0
  35. package/dist/internal/dom/actionable.mjs +62 -0
  36. package/dist/internal/dom/actionable.mjs.map +1 -0
  37. package/dist/internal/kernel-registry.d.mts +8 -0
  38. package/dist/internal/kernel-registry.d.mts.map +1 -0
  39. package/dist/internal/kernel-registry.mjs +31 -0
  40. package/dist/internal/kernel-registry.mjs.map +1 -0
  41. package/dist/internal/kernel.d.mts +267 -0
  42. package/dist/internal/kernel.d.mts.map +1 -0
  43. package/dist/internal/kernel.mjs +322 -0
  44. package/dist/internal/kernel.mjs.map +1 -0
  45. package/dist/internal/otel/exporter.d.mts +93 -0
  46. package/dist/internal/otel/exporter.d.mts.map +1 -0
  47. package/dist/internal/otel/exporter.mjs +212 -0
  48. package/dist/internal/otel/exporter.mjs.map +1 -0
  49. package/dist/internal/otel/index.d.mts +6 -0
  50. package/dist/internal/otel/index.mjs +6 -0
  51. package/dist/internal/otel/instrumentations.d.mts +42 -0
  52. package/dist/internal/otel/instrumentations.d.mts.map +1 -0
  53. package/dist/internal/otel/instrumentations.mjs +150 -0
  54. package/dist/internal/otel/instrumentations.mjs.map +1 -0
  55. package/dist/internal/otel/page-scope-context-manager.d.mts +32 -0
  56. package/dist/internal/otel/page-scope-context-manager.d.mts.map +1 -0
  57. package/dist/internal/otel/page-scope-context-manager.mjs +36 -0
  58. package/dist/internal/otel/page-scope-context-manager.mjs.map +1 -0
  59. package/dist/internal/otel/propagation.d.mts +21 -0
  60. package/dist/internal/otel/propagation.d.mts.map +1 -0
  61. package/dist/internal/otel/propagation.mjs +40 -0
  62. package/dist/internal/otel/propagation.mjs.map +1 -0
  63. package/dist/internal/otel/provider.d.mts +107 -0
  64. package/dist/internal/otel/provider.d.mts.map +1 -0
  65. package/dist/internal/otel/provider.mjs +151 -0
  66. package/dist/internal/otel/provider.mjs.map +1 -0
  67. package/dist/internal/otel/web-vitals.d.mts +35 -0
  68. package/dist/internal/otel/web-vitals.d.mts.map +1 -0
  69. package/dist/internal/otel/web-vitals.mjs +162 -0
  70. package/dist/internal/otel/web-vitals.mjs.map +1 -0
  71. package/dist/internal/page-lifecycle.d.mts +21 -0
  72. package/dist/internal/page-lifecycle.d.mts.map +1 -0
  73. package/dist/internal/page-lifecycle.mjs +33 -0
  74. package/dist/internal/page-lifecycle.mjs.map +1 -0
  75. package/dist/internal/plugin-runtime.d.mts +0 -2
  76. package/dist/internal/plugin-runtime.d.mts.map +1 -1
  77. package/dist/internal/plugin-runtime.mjs +1 -7
  78. package/dist/internal/plugin-runtime.mjs.map +1 -1
  79. package/dist/internal/react-context.d.mts +45 -0
  80. package/dist/internal/react-context.d.mts.map +1 -0
  81. package/dist/internal/react-context.mjs +34 -0
  82. package/dist/internal/react-context.mjs.map +1 -0
  83. package/dist/internal/sw.d.mts +22 -2
  84. package/dist/internal/sw.d.mts.map +1 -1
  85. package/dist/internal/sw.mjs +30 -3
  86. package/dist/internal/sw.mjs.map +1 -1
  87. package/dist/internal/version.d.mts +3 -1
  88. package/dist/internal/version.d.mts.map +1 -1
  89. package/dist/internal/version.mjs +4 -2
  90. package/dist/internal/version.mjs.map +1 -1
  91. package/dist/internal/wrapper-singleton.d.mts +47 -0
  92. package/dist/internal/wrapper-singleton.d.mts.map +1 -0
  93. package/dist/internal/wrapper-singleton.mjs +73 -0
  94. package/dist/internal/wrapper-singleton.mjs.map +1 -0
  95. package/dist/package.mjs +1 -1
  96. package/dist/plugins/errors.d.mts.map +1 -1
  97. package/dist/plugins/errors.mjs +18 -25
  98. package/dist/plugins/errors.mjs.map +1 -1
  99. package/dist/plugins/lib/loader.d.mts +1 -2
  100. package/dist/plugins/lib/loader.d.mts.map +1 -1
  101. package/dist/plugins/lib/loader.mjs +2 -11
  102. package/dist/plugins/lib/loader.mjs.map +1 -1
  103. package/dist/plugins/lib/types.d.mts +3 -2
  104. package/dist/plugins/lib/types.d.mts.map +1 -1
  105. package/dist/plugins/logs.d.mts +13 -0
  106. package/dist/plugins/logs.d.mts.map +1 -0
  107. package/dist/plugins/logs.mjs +53 -0
  108. package/dist/plugins/logs.mjs.map +1 -0
  109. package/dist/plugins/rage-clicks.d.mts.map +1 -1
  110. package/dist/plugins/rage-clicks.mjs +12 -10
  111. package/dist/plugins/rage-clicks.mjs.map +1 -1
  112. package/dist/plugins/replay.d.mts.map +1 -1
  113. package/dist/plugins/replay.mjs +58 -19
  114. package/dist/plugins/replay.mjs.map +1 -1
  115. package/dist/provider.d.mts +11 -20
  116. package/dist/provider.d.mts.map +1 -1
  117. package/dist/provider.mjs +13 -14
  118. package/dist/provider.mjs.map +1 -1
  119. package/dist/react-error-handler.d.mts +21 -5
  120. package/dist/react-error-handler.d.mts.map +1 -1
  121. package/dist/react-error-handler.mjs +15 -7
  122. package/dist/react-error-handler.mjs.map +1 -1
  123. package/dist/sw.d.mts +2 -0
  124. package/dist/sw.mjs +2 -0
  125. package/dist/tracking/api.d.mts +41 -15
  126. package/dist/tracking/api.d.mts.map +1 -1
  127. package/dist/tracking/api.mjs +122 -104
  128. package/dist/tracking/api.mjs.map +1 -1
  129. package/dist/tracking/device.d.mts +30 -7
  130. package/dist/tracking/device.d.mts.map +1 -1
  131. package/dist/tracking/device.mjs +70 -46
  132. package/dist/tracking/device.mjs.map +1 -1
  133. package/dist/tracking/geo.d.mts +11 -3
  134. package/dist/tracking/geo.d.mts.map +1 -1
  135. package/dist/tracking/geo.mjs +33 -29
  136. package/dist/tracking/geo.mjs.map +1 -1
  137. package/dist/tracking/session.d.mts +3 -1
  138. package/dist/tracking/session.d.mts.map +1 -1
  139. package/dist/tracking/session.mjs.map +1 -1
  140. package/dist/util/bot.d.mts +10 -0
  141. package/dist/util/bot.d.mts.map +1 -0
  142. package/dist/util/bot.mjs +14 -0
  143. package/dist/util/bot.mjs.map +1 -0
  144. package/dist/util/global.d.mts +10 -0
  145. package/dist/util/global.d.mts.map +1 -0
  146. package/dist/util/global.mjs +12 -0
  147. package/dist/util/global.mjs.map +1 -0
  148. package/dist/util/log.d.mts.map +1 -1
  149. package/dist/util/log.mjs +8 -1
  150. package/dist/util/log.mjs.map +1 -1
  151. package/dist/util/stringify.d.mts +9 -0
  152. package/dist/util/stringify.d.mts.map +1 -0
  153. package/dist/util/stringify.mjs +16 -0
  154. package/dist/util/stringify.mjs.map +1 -0
  155. package/package.json +73 -20
  156. package/dist/internal/client.d.mts +0 -48
  157. package/dist/internal/client.d.mts.map +0 -1
  158. package/dist/internal/client.mjs +0 -146
  159. package/dist/internal/client.mjs.map +0 -1
  160. package/dist/internal/context.d.mts +0 -6
  161. package/dist/internal/context.d.mts.map +0 -1
  162. package/dist/internal/context.mjs +0 -32
  163. package/dist/internal/context.mjs.map +0 -1
  164. package/dist/internal/envelope.d.mts +0 -15
  165. package/dist/internal/envelope.d.mts.map +0 -1
  166. package/dist/internal/envelope.mjs +0 -24
  167. package/dist/internal/envelope.mjs.map +0 -1
  168. package/dist/internal/errors.d.mts +0 -4
  169. package/dist/internal/errors.d.mts.map +0 -1
  170. package/dist/internal/errors.mjs +0 -4
  171. package/dist/internal/errors.mjs.map +0 -1
  172. package/dist/plugins/device.d.mts +0 -6
  173. package/dist/plugins/device.d.mts.map +0 -1
  174. package/dist/plugins/device.mjs +0 -13
  175. package/dist/plugins/device.mjs.map +0 -1
  176. package/dist/plugins/pages.d.mts +0 -6
  177. package/dist/plugins/pages.d.mts.map +0 -1
  178. package/dist/plugins/pages.mjs +0 -102
  179. package/dist/plugins/pages.mjs.map +0 -1
  180. package/dist/transport/http.d.mts +0 -21
  181. package/dist/transport/http.d.mts.map +0 -1
  182. package/dist/transport/http.mjs +0 -72
  183. package/dist/transport/http.mjs.map +0 -1
  184. package/dist/transport/queue.d.mts +0 -34
  185. package/dist/transport/queue.d.mts.map +0 -1
  186. package/dist/transport/queue.mjs +0 -95
  187. package/dist/transport/queue.mjs.map +0 -1
@@ -0,0 +1,212 @@
1
+ import { PRODUCER_VERSION } from "../version.mjs";
2
+ import { diag } from "@opentelemetry/api";
3
+ import { ExportResultCode } from "@opentelemetry/core";
4
+ import { JsonLogsSerializer } from "@opentelemetry/otlp-transformer/build/esm/logs/json/logs.js";
5
+ import { JsonMetricsSerializer } from "@opentelemetry/otlp-transformer/build/esm/metrics/json/metrics.js";
6
+ import { JsonTraceSerializer } from "@opentelemetry/otlp-transformer/build/esm/trace/json/trace.js";
7
+ import { AggregationTemporality } from "@opentelemetry/sdk-metrics";
8
+ //#region src/internal/otel/exporter.ts
9
+ /**
10
+ * Opaque collector ingest path. Single endpoint for every OTLP signal —
11
+ * server-side dispatches by which `resource*` block the request carries.
12
+ *
13
+ * Semantically nondescript so common adblock filter lists (EasyPrivacy,
14
+ * uBlock) don't fingerprint it the way they do `traces` / `metrics` /
15
+ * `logs` / `telemetry`. Customer-domain proxy mode is still the
16
+ * recommended path for browser SDKs; this is the second line of defence
17
+ * for direct-ingestion (`pub-token`) clients.
18
+ */
19
+ const COLLECTOR_SINK_PATH = "/v2/sink";
20
+ /**
21
+ * Identity gate values the collector accepts via URL query when their
22
+ * matching headers can't be set.
23
+ *
24
+ * `navigator.sendBeacon` (the only browser transport that survives
25
+ * `visibilitychange→hidden`) only attaches `Content-Type` via the
26
+ * `Blob` — no other request headers. We rely on that exclusively for
27
+ * the browser SDK's primary path, so the identity gates the collector
28
+ * enforces (`x-interfere-producer-version`, `x-interfere-pub-token`)
29
+ * have to ride the URL instead. Mirrors `PRODUCER_VERSION_QUERY` /
30
+ * `PUB_TOKEN_QUERY` on
31
+ * `services/collector/src/{modules/v2/middleware,auth/surface}.ts`.
32
+ *
33
+ * Pub-token query exposure note: pub-tokens are public by design (they
34
+ * ship in browser bundles, visible in DevTools), so URL exposure is no
35
+ * worse than the existing header path. API keys deliberately have **no**
36
+ * query fallback — see `surfaceAuth` for the matching server-side
37
+ * rationale.
38
+ */
39
+ const PRODUCER_VERSION_QUERY = "_pv";
40
+ const PUB_TOKEN_QUERY = "_pt";
41
+ const PUB_TOKEN_HEADER = "x-interfere-pub-token";
42
+ const JSON_CONTENT_TYPE = "application/json";
43
+ /** Exported for direct unit testing. */
44
+ function buildSinkUrl(collectorUrl) {
45
+ return `${collectorUrl.endsWith("/") ? collectorUrl.slice(0, -1) : collectorUrl}${COLLECTOR_SINK_PATH}`;
46
+ }
47
+ /**
48
+ * Beacon-flavoured sink URL — encodes the producer-version (always)
49
+ * and pub-token (when present in `authHeaders`) as query parameters
50
+ * so `navigator.sendBeacon` can authenticate without setting custom
51
+ * request headers.
52
+ *
53
+ * Exported for direct unit testing.
54
+ */
55
+ function buildBeaconUrl(input) {
56
+ const params = new URLSearchParams({ [PRODUCER_VERSION_QUERY]: PRODUCER_VERSION });
57
+ const pubToken = input.authHeaders.get(PUB_TOKEN_HEADER);
58
+ if (pubToken) params.set(PUB_TOKEN_QUERY, pubToken);
59
+ return `${buildSinkUrl(input.collectorUrl)}?${params.toString()}`;
60
+ }
61
+ /**
62
+ * Returns true iff the runtime can dispatch a `navigator.sendBeacon`
63
+ * call. Browser-only; server contexts (SSR, prerender, Vitest's `node`
64
+ * environment) trip the typeof guards.
65
+ */
66
+ function canSendBeacon() {
67
+ return typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function";
68
+ }
69
+ /**
70
+ * Dispatches an OTLP payload via `navigator.sendBeacon` and always
71
+ * reports `SUCCESS` to the BSP. `sendBeacon` is fire-and-forget by
72
+ * spec — a `true` return only means "the user agent accepted the
73
+ * payload into its delivery queue", not "the server received it".
74
+ *
75
+ * Why never report `FAILED`:
76
+ * - `BatchSpanProcessor._flushOneBatch` rejects its promise on
77
+ * `code !== SUCCESS`, and that rejection propagates up through
78
+ * `forceFlush()` / `shutdown()`. In production, that surfaces
79
+ * during Vercel function teardown (and other awaited unload
80
+ * paths) as an unhandled rejection. In tests, it surfaces as
81
+ * `provider.test.tsx > passes through useSession` — vitest's
82
+ * `afterEach(close)` awaits `kernel.dispose() → BSP.shutdown
83
+ * → _flushAll`, the rejection bubbles, and the test fails.
84
+ * - `sendBeacon`'s `false` return signals a *local* drop (per-call
85
+ * ~64KiB ceiling, per-page queue full, disallowed scheme). No
86
+ * network request was attempted, so the service-worker backstop
87
+ * in `internal/sw.ts` can't intercept it either. Retrying inside
88
+ * the exporter would just hit the same browser limit again.
89
+ *
90
+ * The data is gone, but the export *completed* in the only sense
91
+ * that matters to the BSP. A `diag.debug` keeps visibility for any
92
+ * OTel diag listener wired into the kernel.
93
+ */
94
+ function reportBeacon(url, payload, signal, resultCallback) {
95
+ if (!payload || payload.byteLength === 0) {
96
+ diag.debug(`[interfere/beacon] ${signal} serializer returned no bytes`);
97
+ resultCallback({ code: ExportResultCode.SUCCESS });
98
+ return;
99
+ }
100
+ if (!canSendBeacon()) {
101
+ diag.debug(`[interfere/beacon] navigator.sendBeacon unavailable; dropping ${signal} batch (${payload.byteLength}B)`);
102
+ resultCallback({ code: ExportResultCode.SUCCESS });
103
+ return;
104
+ }
105
+ const blob = new Blob([payload], { type: JSON_CONTENT_TYPE });
106
+ if (!navigator.sendBeacon(url, blob)) diag.debug(`[interfere/beacon] navigator.sendBeacon refused ${signal} payload (oversized or queue full); dropping ${payload.byteLength}B`);
107
+ resultCallback({ code: ExportResultCode.SUCCESS });
108
+ }
109
+ /**
110
+ * Browser-side OTLP trace exporter.
111
+ *
112
+ * Dispatches every export via `navigator.sendBeacon` — which is the
113
+ * only browser transport that reliably commits a request the page is
114
+ * also tearing down (`visibilitychange→hidden`, hard navigation). The
115
+ * `keepalive: true` fetch path the OTLP HTTP exporter ships with
116
+ * works for small payloads but falls back to ordinary fetch (which
117
+ * the renderer aborts on unload) once the cumulative 64KiB / 9-
118
+ * concurrent budget is exhausted. Production data on 2026-05-11
119
+ * attributed ~15% browser-fetch span loss to that fallback; the
120
+ * beacon path closes that hole by design.
121
+ *
122
+ * Identity (`x-interfere-producer-version`, `x-interfere-pub-token`)
123
+ * rides the URL because beacons can't carry custom headers. The
124
+ * collector accepts both query and header paths — see
125
+ * `services/collector/src/{modules/v2/middleware,auth/surface}.ts`.
126
+ *
127
+ * No retry on failure. The service worker backstop captures 5xx /
128
+ * network failures separately by intercepting the same beacon POST
129
+ * and queueing into IndexedDB for replay (`internal/sw.ts`).
130
+ */
131
+ var BeaconTraceExporter = class {
132
+ url;
133
+ constructor(url) {
134
+ this.url = url;
135
+ }
136
+ export(spans, resultCallback) {
137
+ if (spans.length === 0) {
138
+ resultCallback({ code: ExportResultCode.SUCCESS });
139
+ return;
140
+ }
141
+ reportBeacon(this.url, JsonTraceSerializer.serializeRequest(spans), "trace", resultCallback);
142
+ }
143
+ shutdown() {
144
+ return Promise.resolve();
145
+ }
146
+ forceFlush() {
147
+ return Promise.resolve();
148
+ }
149
+ };
150
+ /**
151
+ * Browser-side OTLP log exporter — same beacon transport, log
152
+ * payload. See `BeaconTraceExporter` for the design rationale.
153
+ */
154
+ var BeaconLogExporter = class {
155
+ url;
156
+ constructor(url) {
157
+ this.url = url;
158
+ }
159
+ export(logs, resultCallback) {
160
+ if (logs.length === 0) {
161
+ resultCallback({ code: ExportResultCode.SUCCESS });
162
+ return;
163
+ }
164
+ reportBeacon(this.url, JsonLogsSerializer.serializeRequest(logs), "log", resultCallback);
165
+ }
166
+ shutdown() {
167
+ return Promise.resolve();
168
+ }
169
+ forceFlush() {
170
+ return Promise.resolve();
171
+ }
172
+ };
173
+ /**
174
+ * Browser-side OTLP metric exporter — beacon transport, metric
175
+ * payload, `DELTA` temporality.
176
+ *
177
+ * `DELTA` matches what the OTLP HTTP exporter the kit's previous
178
+ * `OTLPMetricExporter` was configured with (see the
179
+ * `temporalityPreference: AggregationTemporalityPreference.DELTA`
180
+ * arg `provider.ts` used to pass). Returning the same temporality
181
+ * for every instrument type keeps the wire shape downstream
182
+ * consumers receive unchanged across the migration.
183
+ */
184
+ var BeaconMetricExporter = class {
185
+ url;
186
+ constructor(url) {
187
+ this.url = url;
188
+ }
189
+ export(metrics, resultCallback) {
190
+ reportBeacon(this.url, JsonMetricsSerializer.serializeRequest(metrics), "metric", resultCallback);
191
+ }
192
+ selectAggregationTemporality() {
193
+ return AggregationTemporality.DELTA;
194
+ }
195
+ forceFlush() {
196
+ return Promise.resolve();
197
+ }
198
+ shutdown() {
199
+ return Promise.resolve();
200
+ }
201
+ };
202
+ function createBeaconTraceExporter(input) {
203
+ return new BeaconTraceExporter(buildBeaconUrl(input));
204
+ }
205
+ function createBeaconMetricExporter(input) {
206
+ return new BeaconMetricExporter(buildBeaconUrl(input));
207
+ }
208
+ function createBeaconLogExporter(input) {
209
+ return new BeaconLogExporter(buildBeaconUrl(input));
210
+ }
211
+ //#endregion
212
+ export { buildBeaconUrl, buildSinkUrl, createBeaconLogExporter, createBeaconMetricExporter, createBeaconTraceExporter };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exporter.mjs","names":[],"sources":["../../../src/internal/otel/exporter.ts"],"sourcesContent":["import { diag } from \"@opentelemetry/api\";\nimport type { ExportResult } from \"@opentelemetry/core\";\nimport { ExportResultCode } from \"@opentelemetry/core\";\n// Deep imports against the package build output — the barrel\n// (`@opentelemetry/otlp-transformer`) re-exports the JSON *and*\n// Protobuf serializers, and the protobuf side reaches `protobufjs`\n// which transitively hits `worker_threads.MessageChannel` (via\n// `import-in-the-middle`). Vite externalizes `worker_threads` for\n// the browser test environment with a runtime-throwing stub; the\n// kernel's `await import(\"./otel/index.js\")` chunk inherits any file\n// reachable from the package root through Vite's dep optimizer, so\n// importing from the barrel breaks `provider.test.tsx` with\n// \"Failed to fetch dynamically imported module\" on the chunk URL.\n//\n// The `*/json/trace.js` (etc.) modules import only `@opentelemetry/\n// api` and the package's own JSON-only utility files — no protobuf\n// in the transitive graph. We also pin the file extension (`.js`)\n// instead of letting the directory's `index.js` resolution kick in\n// because resolvers (bun on Linux vs macOS, vite vs raw esbuild)\n// disagree about whether a missing extension on a directory-shaped\n// specifier should resolve to `<dir>/index.js` or fail. The explicit\n// `.js` is the only spelling all of them agree on.\n//\n// `@opentelemetry/otlp-transformer` doesn't ship an `\"exports\"` field\n// in its `package.json`, so these deep paths are the supported escape\n// hatch (the OTel HTTP exporters take the same approach internally —\n// see `@opentelemetry/exporter-trace-otlp-http/build/esm/platform/\n// browser/OTLPTraceExporter.js`). Pinned via the workspace catalog so\n// a minor version bump can't quietly relocate the build directory.\nimport { JsonLogsSerializer } from \"@opentelemetry/otlp-transformer/build/esm/logs/json/logs.js\";\nimport { JsonMetricsSerializer } from \"@opentelemetry/otlp-transformer/build/esm/metrics/json/metrics.js\";\nimport { JsonTraceSerializer } from \"@opentelemetry/otlp-transformer/build/esm/trace/json/trace.js\";\nimport type { ReadableLogRecord } from \"@opentelemetry/sdk-logs\";\nimport type {\n PushMetricExporter,\n ResourceMetrics,\n} from \"@opentelemetry/sdk-metrics\";\nimport { AggregationTemporality } from \"@opentelemetry/sdk-metrics\";\nimport type { ReadableSpan, SpanExporter } from \"@opentelemetry/sdk-trace-base\";\n\nimport { PRODUCER_VERSION } from \"../version.js\";\n\n/**\n * Opaque collector ingest path. Single endpoint for every OTLP signal —\n * server-side dispatches by which `resource*` block the request carries.\n *\n * Semantically nondescript so common adblock filter lists (EasyPrivacy,\n * uBlock) don't fingerprint it the way they do `traces` / `metrics` /\n * `logs` / `telemetry`. Customer-domain proxy mode is still the\n * recommended path for browser SDKs; this is the second line of defence\n * for direct-ingestion (`pub-token`) clients.\n */\nconst COLLECTOR_SINK_PATH = \"/v2/sink\";\n\n/**\n * Identity gate values the collector accepts via URL query when their\n * matching headers can't be set.\n *\n * `navigator.sendBeacon` (the only browser transport that survives\n * `visibilitychange→hidden`) only attaches `Content-Type` via the\n * `Blob` — no other request headers. We rely on that exclusively for\n * the browser SDK's primary path, so the identity gates the collector\n * enforces (`x-interfere-producer-version`, `x-interfere-pub-token`)\n * have to ride the URL instead. Mirrors `PRODUCER_VERSION_QUERY` /\n * `PUB_TOKEN_QUERY` on\n * `services/collector/src/{modules/v2/middleware,auth/surface}.ts`.\n *\n * Pub-token query exposure note: pub-tokens are public by design (they\n * ship in browser bundles, visible in DevTools), so URL exposure is no\n * worse than the existing header path. API keys deliberately have **no**\n * query fallback — see `surfaceAuth` for the matching server-side\n * rationale.\n */\nconst PRODUCER_VERSION_QUERY = \"_pv\";\nconst PUB_TOKEN_QUERY = \"_pt\";\nconst PUB_TOKEN_HEADER = \"x-interfere-pub-token\";\n\nconst JSON_CONTENT_TYPE = \"application/json\";\n\ninterface ExporterCommon {\n /**\n * Auth identity from the kernel's resolved ingest target — currently\n * `x-interfere-pub-token` only (proxy mode sets nothing because the\n * proxy server stamps `x-api-key` upstream). Encoded into the beacon\n * URL via `buildBeaconUrl` rather than into request headers because\n * `sendBeacon` doesn't carry custom headers.\n */\n authHeaders: Headers;\n /** Base collector URL — the opaque sink path is appended. */\n collectorUrl: string;\n}\n\n/** Exported for direct unit testing. */\nexport function buildSinkUrl(collectorUrl: string): string {\n const stripped = collectorUrl.endsWith(\"/\")\n ? collectorUrl.slice(0, -1)\n : collectorUrl;\n return `${stripped}${COLLECTOR_SINK_PATH}`;\n}\n\n/**\n * Beacon-flavoured sink URL — encodes the producer-version (always)\n * and pub-token (when present in `authHeaders`) as query parameters\n * so `navigator.sendBeacon` can authenticate without setting custom\n * request headers.\n *\n * Exported for direct unit testing.\n */\nexport function buildBeaconUrl(input: ExporterCommon): string {\n const params = new URLSearchParams({\n [PRODUCER_VERSION_QUERY]: PRODUCER_VERSION,\n });\n const pubToken = input.authHeaders.get(PUB_TOKEN_HEADER);\n if (pubToken) {\n params.set(PUB_TOKEN_QUERY, pubToken);\n }\n return `${buildSinkUrl(input.collectorUrl)}?${params.toString()}`;\n}\n\n/**\n * Returns true iff the runtime can dispatch a `navigator.sendBeacon`\n * call. Browser-only; server contexts (SSR, prerender, Vitest's `node`\n * environment) trip the typeof guards.\n */\nfunction canSendBeacon(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n typeof navigator.sendBeacon === \"function\"\n );\n}\n\n/**\n * Dispatches an OTLP payload via `navigator.sendBeacon` and always\n * reports `SUCCESS` to the BSP. `sendBeacon` is fire-and-forget by\n * spec — a `true` return only means \"the user agent accepted the\n * payload into its delivery queue\", not \"the server received it\".\n *\n * Why never report `FAILED`:\n * - `BatchSpanProcessor._flushOneBatch` rejects its promise on\n * `code !== SUCCESS`, and that rejection propagates up through\n * `forceFlush()` / `shutdown()`. In production, that surfaces\n * during Vercel function teardown (and other awaited unload\n * paths) as an unhandled rejection. In tests, it surfaces as\n * `provider.test.tsx > passes through useSession` — vitest's\n * `afterEach(close)` awaits `kernel.dispose() → BSP.shutdown\n * → _flushAll`, the rejection bubbles, and the test fails.\n * - `sendBeacon`'s `false` return signals a *local* drop (per-call\n * ~64KiB ceiling, per-page queue full, disallowed scheme). No\n * network request was attempted, so the service-worker backstop\n * in `internal/sw.ts` can't intercept it either. Retrying inside\n * the exporter would just hit the same browser limit again.\n *\n * The data is gone, but the export *completed* in the only sense\n * that matters to the BSP. A `diag.debug` keeps visibility for any\n * OTel diag listener wired into the kernel.\n */\nfunction reportBeacon(\n url: string,\n payload: Uint8Array | undefined,\n signal: \"trace\" | \"log\" | \"metric\",\n resultCallback: (result: ExportResult) => void\n): void {\n if (!payload || payload.byteLength === 0) {\n diag.debug(`[interfere/beacon] ${signal} serializer returned no bytes`);\n resultCallback({ code: ExportResultCode.SUCCESS });\n return;\n }\n if (!canSendBeacon()) {\n diag.debug(\n `[interfere/beacon] navigator.sendBeacon unavailable; dropping ${signal} batch (${payload.byteLength}B)`\n );\n resultCallback({ code: ExportResultCode.SUCCESS });\n return;\n }\n // `Blob` is the only sendBeacon argument shape that lets us pin\n // `Content-Type: application/json` on the request — using the raw\n // `Uint8Array` would default to `application/octet-stream`, which\n // the collector's content-type allowlist rejects with 415.\n //\n // The cast is necessary because `JsonTraceSerializer.serializeRequest`\n // returns `Uint8Array<ArrayBufferLike>`, but `Blob`'s constructor\n // narrows `BlobPart` to `Uint8Array<ArrayBuffer>` (excluding\n // `SharedArrayBuffer`). The serializer never returns shared-buffer-\n // backed bytes (it allocates a fresh `Uint8Array(byteLength)`\n // internally) so the runtime check the type system is asking for is\n // already true; a structural cast preserves that without forcing an\n // unnecessary buffer copy.\n const blob = new Blob([payload as Uint8Array<ArrayBuffer>], {\n type: JSON_CONTENT_TYPE,\n });\n if (!navigator.sendBeacon(url, blob)) {\n diag.debug(\n `[interfere/beacon] navigator.sendBeacon refused ${signal} payload (oversized or queue full); dropping ${payload.byteLength}B`\n );\n }\n resultCallback({ code: ExportResultCode.SUCCESS });\n}\n\n/**\n * Browser-side OTLP trace exporter.\n *\n * Dispatches every export via `navigator.sendBeacon` — which is the\n * only browser transport that reliably commits a request the page is\n * also tearing down (`visibilitychange→hidden`, hard navigation). The\n * `keepalive: true` fetch path the OTLP HTTP exporter ships with\n * works for small payloads but falls back to ordinary fetch (which\n * the renderer aborts on unload) once the cumulative 64KiB / 9-\n * concurrent budget is exhausted. Production data on 2026-05-11\n * attributed ~15% browser-fetch span loss to that fallback; the\n * beacon path closes that hole by design.\n *\n * Identity (`x-interfere-producer-version`, `x-interfere-pub-token`)\n * rides the URL because beacons can't carry custom headers. The\n * collector accepts both query and header paths — see\n * `services/collector/src/{modules/v2/middleware,auth/surface}.ts`.\n *\n * No retry on failure. The service worker backstop captures 5xx /\n * network failures separately by intercepting the same beacon POST\n * and queueing into IndexedDB for replay (`internal/sw.ts`).\n */\nclass BeaconTraceExporter implements SpanExporter {\n private readonly url: string;\n\n constructor(url: string) {\n this.url = url;\n }\n\n export(\n spans: ReadableSpan[],\n resultCallback: (result: ExportResult) => void\n ): void {\n if (spans.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS });\n return;\n }\n reportBeacon(\n this.url,\n JsonTraceSerializer.serializeRequest(spans),\n \"trace\",\n resultCallback\n );\n }\n\n shutdown(): Promise<void> {\n return Promise.resolve();\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n}\n\n/**\n * Browser-side OTLP log exporter — same beacon transport, log\n * payload. See `BeaconTraceExporter` for the design rationale.\n */\nclass BeaconLogExporter {\n private readonly url: string;\n\n constructor(url: string) {\n this.url = url;\n }\n\n export(\n logs: ReadableLogRecord[],\n resultCallback: (result: ExportResult) => void\n ): void {\n if (logs.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS });\n return;\n }\n reportBeacon(\n this.url,\n JsonLogsSerializer.serializeRequest(logs),\n \"log\",\n resultCallback\n );\n }\n\n shutdown(): Promise<void> {\n return Promise.resolve();\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n}\n\n/**\n * Browser-side OTLP metric exporter — beacon transport, metric\n * payload, `DELTA` temporality.\n *\n * `DELTA` matches what the OTLP HTTP exporter the kit's previous\n * `OTLPMetricExporter` was configured with (see the\n * `temporalityPreference: AggregationTemporalityPreference.DELTA`\n * arg `provider.ts` used to pass). Returning the same temporality\n * for every instrument type keeps the wire shape downstream\n * consumers receive unchanged across the migration.\n */\nclass BeaconMetricExporter implements PushMetricExporter {\n private readonly url: string;\n\n constructor(url: string) {\n this.url = url;\n }\n\n export(\n metrics: ResourceMetrics,\n resultCallback: (result: ExportResult) => void\n ): void {\n reportBeacon(\n this.url,\n JsonMetricsSerializer.serializeRequest(metrics),\n \"metric\",\n resultCallback\n );\n }\n\n selectAggregationTemporality(): AggregationTemporality {\n return AggregationTemporality.DELTA;\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n\n shutdown(): Promise<void> {\n return Promise.resolve();\n }\n}\n\nexport function createBeaconTraceExporter(\n input: ExporterCommon\n): BeaconTraceExporter {\n return new BeaconTraceExporter(buildBeaconUrl(input));\n}\n\nexport function createBeaconMetricExporter(\n input: ExporterCommon\n): BeaconMetricExporter {\n return new BeaconMetricExporter(buildBeaconUrl(input));\n}\n\nexport function createBeaconLogExporter(\n input: ExporterCommon\n): BeaconLogExporter {\n return new BeaconLogExporter(buildBeaconUrl(input));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAoDA,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;AAqB5B,MAAM,yBAAyB;AAC/B,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAEzB,MAAM,oBAAoB;;AAgB1B,SAAgB,aAAa,cAA8B;CAIzD,OAAO,GAHU,aAAa,SAAS,IAAI,GACvC,aAAa,MAAM,GAAG,GAAG,GACzB,eACiB;;;;;;;;;;AAWvB,SAAgB,eAAe,OAA+B;CAC5D,MAAM,SAAS,IAAI,gBAAgB,GAChC,yBAAyB,kBAC3B,CAAC;CACF,MAAM,WAAW,MAAM,YAAY,IAAI,iBAAiB;CACxD,IAAI,UACF,OAAO,IAAI,iBAAiB,SAAS;CAEvC,OAAO,GAAG,aAAa,MAAM,aAAa,CAAC,GAAG,OAAO,UAAU;;;;;;;AAQjE,SAAS,gBAAyB;CAChC,OACE,OAAO,cAAc,eACrB,OAAO,UAAU,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BpC,SAAS,aACP,KACA,SACA,QACA,gBACM;CACN,IAAI,CAAC,WAAW,QAAQ,eAAe,GAAG;EACxC,KAAK,MAAM,sBAAsB,OAAO,+BAA+B;EACvE,eAAe,EAAE,MAAM,iBAAiB,SAAS,CAAC;EAClD;;CAEF,IAAI,CAAC,eAAe,EAAE;EACpB,KAAK,MACH,iEAAiE,OAAO,UAAU,QAAQ,WAAW,IACtG;EACD,eAAe,EAAE,MAAM,iBAAiB,SAAS,CAAC;EAClD;;CAeF,MAAM,OAAO,IAAI,KAAK,CAAC,QAAmC,EAAE,EAC1D,MAAM,mBACP,CAAC;CACF,IAAI,CAAC,UAAU,WAAW,KAAK,KAAK,EAClC,KAAK,MACH,mDAAmD,OAAO,+CAA+C,QAAQ,WAAW,GAC7H;CAEH,eAAe,EAAE,MAAM,iBAAiB,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;AAyBpD,IAAM,sBAAN,MAAkD;CAChD;CAEA,YAAY,KAAa;EACvB,KAAK,MAAM;;CAGb,OACE,OACA,gBACM;EACN,IAAI,MAAM,WAAW,GAAG;GACtB,eAAe,EAAE,MAAM,iBAAiB,SAAS,CAAC;GAClD;;EAEF,aACE,KAAK,KACL,oBAAoB,iBAAiB,MAAM,EAC3C,SACA,eACD;;CAGH,WAA0B;EACxB,OAAO,QAAQ,SAAS;;CAG1B,aAA4B;EAC1B,OAAO,QAAQ,SAAS;;;;;;;AAQ5B,IAAM,oBAAN,MAAwB;CACtB;CAEA,YAAY,KAAa;EACvB,KAAK,MAAM;;CAGb,OACE,MACA,gBACM;EACN,IAAI,KAAK,WAAW,GAAG;GACrB,eAAe,EAAE,MAAM,iBAAiB,SAAS,CAAC;GAClD;;EAEF,aACE,KAAK,KACL,mBAAmB,iBAAiB,KAAK,EACzC,OACA,eACD;;CAGH,WAA0B;EACxB,OAAO,QAAQ,SAAS;;CAG1B,aAA4B;EAC1B,OAAO,QAAQ,SAAS;;;;;;;;;;;;;;AAe5B,IAAM,uBAAN,MAAyD;CACvD;CAEA,YAAY,KAAa;EACvB,KAAK,MAAM;;CAGb,OACE,SACA,gBACM;EACN,aACE,KAAK,KACL,sBAAsB,iBAAiB,QAAQ,EAC/C,UACA,eACD;;CAGH,+BAAuD;EACrD,OAAO,uBAAuB;;CAGhC,aAA4B;EAC1B,OAAO,QAAQ,SAAS;;CAG1B,WAA0B;EACxB,OAAO,QAAQ,SAAS;;;AAI5B,SAAgB,0BACd,OACqB;CACrB,OAAO,IAAI,oBAAoB,eAAe,MAAM,CAAC;;AAGvD,SAAgB,2BACd,OACsB;CACtB,OAAO,IAAI,qBAAqB,eAAe,MAAM,CAAC;;AAGxD,SAAgB,wBACd,OACmB;CACnB,OAAO,IAAI,kBAAkB,eAAe,MAAM,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { createBeaconMetricExporter, createBeaconTraceExporter } from "./exporter.mjs";
2
+ import { InstrumentationsInput, registerBundledInstrumentations } from "./instrumentations.mjs";
3
+ import { readPropagationFromDocument } from "./propagation.mjs";
4
+ import { OtelProviderHandle, OtelProviderInput, buildOtelProvider } from "./provider.mjs";
5
+ import { captureWebVitals } from "./web-vitals.mjs";
6
+ export { type InstrumentationsInput, type OtelProviderHandle, type OtelProviderInput, buildOtelProvider, captureWebVitals, createBeaconMetricExporter, createBeaconTraceExporter, readPropagationFromDocument, registerBundledInstrumentations };
@@ -0,0 +1,6 @@
1
+ import { createBeaconMetricExporter, createBeaconTraceExporter } from "./exporter.mjs";
2
+ import { registerBundledInstrumentations } from "./instrumentations.mjs";
3
+ import { readPropagationFromDocument } from "./propagation.mjs";
4
+ import { buildOtelProvider } from "./provider.mjs";
5
+ import { captureWebVitals } from "./web-vitals.mjs";
6
+ export { buildOtelProvider, captureWebVitals, createBeaconMetricExporter, createBeaconTraceExporter, readPropagationFromDocument, registerBundledInstrumentations };
@@ -0,0 +1,42 @@
1
+ import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
2
+
3
+ //#region src/internal/otel/instrumentations.d.ts
4
+ interface InstrumentationsInput {
5
+ /**
6
+ * URLs the SDK exempts from `fetch` and `XHR` instrumentation —
7
+ * typically our own ingest/OTLP endpoints, so the SDK doesn't trace
8
+ * its own export requests in an infinite loop. Combined with the
9
+ * `THIRD_PARTY_IGNORE` defaults and the customer's `ignoreUrls`.
10
+ */
11
+ ignoreUrls: (string | RegExp)[];
12
+ /**
13
+ * Customer-supplied URL patterns the fetch + XHR instrumentations
14
+ * inject `traceparent` + `baggage` headers on. Same-origin requests
15
+ * always propagate.
16
+ */
17
+ propagateContextUrls?: (string | RegExp)[];
18
+ /**
19
+ * Pathname → low-cardinality route template (e.g. `/blog/[slug]`).
20
+ * Drives span renaming + `url.path` / `http.route` attrs across
21
+ * fetch, document-load, user-interaction, and long-task.
22
+ */
23
+ resolveRoute?: (pathname: string) => string | undefined;
24
+ /**
25
+ * The kernel's private provider — instrumentations register against
26
+ * it, not the global one, so customer OTel setups stay untouched.
27
+ */
28
+ tracerProvider: WebTracerProvider;
29
+ }
30
+ /**
31
+ * Registers every browser auto-instrumentation against the kernel's
32
+ * private provider. Every instrumentation is enriched with the same
33
+ * route-template-aware URL attributes so dashboards can slice fetch /
34
+ * interaction / long-task / resource-load by `http.route` without
35
+ * cardinality blow-up from raw pathnames.
36
+ *
37
+ * Returns a disposer that unregisters everything; called by
38
+ * `kernel.dispose()`.
39
+ */
40
+ declare function registerBundledInstrumentations(input: InstrumentationsInput): () => void;
41
+ //#endregion
42
+ export { InstrumentationsInput, registerBundledInstrumentations };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentations.d.mts","names":[],"sources":["../../../src/internal/otel/instrumentations.ts"],"mappings":";;;UAgHiB,qBAAA;;AAAjB;;;;;EAOE,UAAA,YAAsB,MAAA;EAiBW;;;;;EAXjC,oBAAA,aAAiC,MAAA;EAMjC;;;;;EAAA,YAAA,IAAgB,QAAA;EAkBF;;;;EAbd,cAAA,EAAgB,iBAAA;AAAA;;;;;;;;;;;iBAaF,+BAAA,CACd,KAAA,EAAO,qBAAA"}
@@ -0,0 +1,150 @@
1
+ import { describeActionable, isActionable } from "../dom/actionable.mjs";
2
+ import { registerInstrumentations } from "@opentelemetry/instrumentation";
3
+ import { BrowserNavigationInstrumentation, defaultSanitizeUrl } from "@opentelemetry/instrumentation-browser-navigation";
4
+ import { DocumentLoadInstrumentation } from "@opentelemetry/instrumentation-document-load";
5
+ import { FetchInstrumentation } from "@opentelemetry/instrumentation-fetch";
6
+ import { LongTaskInstrumentation } from "@opentelemetry/instrumentation-long-task";
7
+ import { UserInteractionInstrumentation } from "@opentelemetry/instrumentation-user-interaction";
8
+ import { XMLHttpRequestInstrumentation } from "@opentelemetry/instrumentation-xml-http-request";
9
+ //#region src/internal/otel/instrumentations.ts
10
+ const ATTR_HTTP_ROUTE = "http.route";
11
+ const ATTR_URL_FULL = "url.full";
12
+ const ATTR_URL_PATH = "url.path";
13
+ /**
14
+ * Mirrors the `semconvStabilityOptIn` constant from the internal
15
+ * `observability/browser/rum.ts`. Pins the HTTP attribute set to the
16
+ * stable-namespace conventions (`url.path`, `server.address`, …) so
17
+ * dashboards that filter on those keys aren't broken by minor
18
+ * upgrades of the instrumentation packages.
19
+ */
20
+ const SEMCONV_STABILITY_OPT_IN = "http,database,messaging,gen_ai_latest_experimental";
21
+ /**
22
+ * Hosts/paths whose CLIENT fetch spans we never want to emit: they
23
+ * always become 1-span orphan traces (no traceparent propagation to
24
+ * 3rd-party origins) and clutter live tail. Conservative, customer-
25
+ * generic list — common analytics / ads / auth / replay vendors that
26
+ * many apps use. Customer-specific noise (Statsig CDNs, our own
27
+ * BetterStack, etc.) belongs in the customer's own `ignoreUrls` config,
28
+ * not hardcoded into a public SDK.
29
+ */
30
+ const THIRD_PARTY_IGNORE = [
31
+ /(?:^|\/\/)clerk\./,
32
+ /\.clerk\./,
33
+ /\.sentry\./,
34
+ /\.intercom\./,
35
+ /\.posthog\./,
36
+ /\.googletagmanager\.com/,
37
+ /\.google-analytics\.com/,
38
+ /\.googleapis\.com/,
39
+ /\.doubleclick\.net/,
40
+ /\.facebook\.com/,
41
+ /\.fbcdn\.net/,
42
+ /\.analytics\.google\.com/,
43
+ /px\.ads\.linkedin\.com/,
44
+ /[?&]_rsc=/
45
+ ];
46
+ const NEVER_MATCH = /^$/;
47
+ const ESCAPE_RE = /[.*+?^${}()|[\]\\]/g;
48
+ function sameOriginPattern() {
49
+ if (typeof window === "undefined") return NEVER_MATCH;
50
+ const { protocol, host } = window.location;
51
+ return new RegExp(`^${protocol}//${host.replace(ESCAPE_RE, "\\$&")}`);
52
+ }
53
+ function stampInteractionAttrs(eventType, element, span) {
54
+ const desc = describeActionable(element);
55
+ span.setAttribute("ui.event_type", eventType);
56
+ span.setAttribute("ui.target.tag", desc.tag);
57
+ if (desc.id) span.setAttribute("ui.target.id", desc.id);
58
+ if (desc.name) span.setAttribute("ui.target.name", desc.name);
59
+ if (desc.role) span.setAttribute("ui.target.role", desc.role);
60
+ if (desc.ariaLabel) span.setAttribute("ui.target.aria_label", desc.ariaLabel);
61
+ if (desc.text) span.setAttribute("ui.target.text", desc.text);
62
+ }
63
+ function stampUrlAttrs(span, resolveRoute) {
64
+ if (typeof window === "undefined") return;
65
+ const pathname = window.location.pathname;
66
+ const route = resolveRoute?.(pathname);
67
+ span.setAttribute(ATTR_URL_PATH, route ?? pathname);
68
+ span.setAttribute(ATTR_URL_FULL, window.location.href);
69
+ if (route) span.setAttribute(ATTR_HTTP_ROUTE, route);
70
+ }
71
+ /**
72
+ * Registers every browser auto-instrumentation against the kernel's
73
+ * private provider. Every instrumentation is enriched with the same
74
+ * route-template-aware URL attributes so dashboards can slice fetch /
75
+ * interaction / long-task / resource-load by `http.route` without
76
+ * cardinality blow-up from raw pathnames.
77
+ *
78
+ * Returns a disposer that unregisters everything; called by
79
+ * `kernel.dispose()`.
80
+ */
81
+ function registerBundledInstrumentations(input) {
82
+ const { tracerProvider, ignoreUrls, propagateContextUrls = [] } = input;
83
+ const resolveRoute = input.resolveRoute;
84
+ const origin = sameOriginPattern();
85
+ const fetchIgnore = [...ignoreUrls, ...THIRD_PARTY_IGNORE];
86
+ return registerInstrumentations({
87
+ tracerProvider,
88
+ instrumentations: [
89
+ new FetchInstrumentation({
90
+ propagateTraceHeaderCorsUrls: [origin, ...propagateContextUrls],
91
+ ignoreUrls: fetchIgnore,
92
+ semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,
93
+ measureRequestSize: true,
94
+ applyCustomAttributesOnSpan: (span, request) => {
95
+ const url = request instanceof Request ? request.url : request?.toString();
96
+ if (typeof url !== "string") return;
97
+ try {
98
+ const parsed = new URL(url);
99
+ span.setAttribute(ATTR_URL_PATH, parsed.pathname);
100
+ if (origin.test(url) || propagateContextUrls.some((re) => re instanceof RegExp ? re.test(url) : url.includes(re))) {
101
+ const method = (request instanceof Request ? request.method : void 0) ?? "GET";
102
+ const route = resolveRoute?.(parsed.pathname);
103
+ if (route) span.setAttribute(ATTR_HTTP_ROUTE, route);
104
+ span.updateName(`${method} ${route ?? parsed.pathname}`);
105
+ }
106
+ } catch {
107
+ return;
108
+ }
109
+ }
110
+ }),
111
+ new XMLHttpRequestInstrumentation({
112
+ propagateTraceHeaderCorsUrls: [origin, ...propagateContextUrls],
113
+ ignoreUrls: fetchIgnore,
114
+ semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN
115
+ }),
116
+ new DocumentLoadInstrumentation({
117
+ semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,
118
+ applyCustomAttributesOnSpan: { resourceFetch: (span, entry) => {
119
+ const name = entry?.name;
120
+ if (typeof name !== "string") return;
121
+ try {
122
+ const url = new URL(name);
123
+ const isCrossOrigin = typeof window !== "undefined" && url.origin !== window.location.origin;
124
+ span.updateName(isCrossOrigin ? `resource: ${url.host}${url.pathname}` : `resource: ${url.pathname}`);
125
+ span.setAttribute(ATTR_URL_FULL, name);
126
+ span.setAttribute(ATTR_URL_PATH, url.pathname);
127
+ span.setAttribute("server.address", url.host);
128
+ } catch {}
129
+ } }
130
+ }),
131
+ new BrowserNavigationInstrumentation({ sanitizeUrl: defaultSanitizeUrl }),
132
+ new UserInteractionInstrumentation({
133
+ eventNames: ["click", "submit"],
134
+ shouldPreventSpanCreation: (eventType, element, span) => {
135
+ if (eventType === "click" && !isActionable(element)) return true;
136
+ stampInteractionAttrs(eventType, element, span);
137
+ stampUrlAttrs(span, resolveRoute);
138
+ return false;
139
+ }
140
+ }),
141
+ new LongTaskInstrumentation({ observerCallback: (span, info) => {
142
+ stampUrlAttrs(span, resolveRoute);
143
+ const containerType = info.longtaskEntry.attribution[0]?.containerType;
144
+ if (containerType) span.setAttribute("longtask.container_type", containerType);
145
+ } })
146
+ ]
147
+ });
148
+ }
149
+ //#endregion
150
+ export { registerBundledInstrumentations };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumentations.mjs","names":[],"sources":["../../../src/internal/otel/instrumentations.ts"],"sourcesContent":["import type { Span } from \"@opentelemetry/api\";\nimport { registerInstrumentations } from \"@opentelemetry/instrumentation\";\nimport {\n BrowserNavigationInstrumentation,\n defaultSanitizeUrl,\n} from \"@opentelemetry/instrumentation-browser-navigation\";\nimport { DocumentLoadInstrumentation } from \"@opentelemetry/instrumentation-document-load\";\nimport { FetchInstrumentation } from \"@opentelemetry/instrumentation-fetch\";\nimport {\n LongTaskInstrumentation,\n type ObserverCallbackInformation,\n} from \"@opentelemetry/instrumentation-long-task\";\nimport { UserInteractionInstrumentation } from \"@opentelemetry/instrumentation-user-interaction\";\nimport { XMLHttpRequestInstrumentation } from \"@opentelemetry/instrumentation-xml-http-request\";\nimport type { WebTracerProvider } from \"@opentelemetry/sdk-trace-web\";\n\nimport { describeActionable, isActionable } from \"../dom/actionable.js\";\n\nconst ATTR_HTTP_ROUTE = \"http.route\" as const;\nconst ATTR_URL_FULL = \"url.full\" as const;\nconst ATTR_URL_PATH = \"url.path\" as const;\n\n/**\n * Mirrors the `semconvStabilityOptIn` constant from the internal\n * `observability/browser/rum.ts`. Pins the HTTP attribute set to the\n * stable-namespace conventions (`url.path`, `server.address`, …) so\n * dashboards that filter on those keys aren't broken by minor\n * upgrades of the instrumentation packages.\n */\nconst SEMCONV_STABILITY_OPT_IN =\n \"http,database,messaging,gen_ai_latest_experimental\";\n\n/**\n * Hosts/paths whose CLIENT fetch spans we never want to emit: they\n * always become 1-span orphan traces (no traceparent propagation to\n * 3rd-party origins) and clutter live tail. Conservative, customer-\n * generic list — common analytics / ads / auth / replay vendors that\n * many apps use. Customer-specific noise (Statsig CDNs, our own\n * BetterStack, etc.) belongs in the customer's own `ignoreUrls` config,\n * not hardcoded into a public SDK.\n */\nconst THIRD_PARTY_IGNORE: RegExp[] = [\n /(?:^|\\/\\/)clerk\\./,\n /\\.clerk\\./,\n /\\.sentry\\./,\n /\\.intercom\\./,\n /\\.posthog\\./,\n /\\.googletagmanager\\.com/,\n /\\.google-analytics\\.com/,\n /\\.googleapis\\.com/,\n /\\.doubleclick\\.net/,\n /\\.facebook\\.com/,\n /\\.fbcdn\\.net/,\n /\\.analytics\\.google\\.com/,\n /px\\.ads\\.linkedin\\.com/,\n /[?&]_rsc=/,\n];\n\nconst NEVER_MATCH = /^$/;\nconst ESCAPE_RE = /[.*+?^${}()|[\\]\\\\]/g;\n\nfunction sameOriginPattern(): RegExp {\n if (typeof window === \"undefined\") {\n return NEVER_MATCH;\n }\n const { protocol, host } = window.location;\n return new RegExp(`^${protocol}//${host.replace(ESCAPE_RE, \"\\\\$&\")}`);\n}\n\nfunction stampInteractionAttrs(\n eventType: string,\n element: HTMLElement,\n span: Span\n): void {\n const desc = describeActionable(element);\n span.setAttribute(\"ui.event_type\", eventType);\n span.setAttribute(\"ui.target.tag\", desc.tag);\n if (desc.id) {\n span.setAttribute(\"ui.target.id\", desc.id);\n }\n if (desc.name) {\n span.setAttribute(\"ui.target.name\", desc.name);\n }\n if (desc.role) {\n span.setAttribute(\"ui.target.role\", desc.role);\n }\n if (desc.ariaLabel) {\n span.setAttribute(\"ui.target.aria_label\", desc.ariaLabel);\n }\n // Truncated visible label — useful for triage without exfiltrating\n // full DOM contents. Only meaningful on the direct target.\n if (desc.text) {\n span.setAttribute(\"ui.target.text\", desc.text);\n }\n}\n\nfunction stampUrlAttrs(\n span: Span,\n resolveRoute: ((pathname: string) => string | undefined) | undefined\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n const pathname = window.location.pathname;\n const route = resolveRoute?.(pathname);\n span.setAttribute(ATTR_URL_PATH, route ?? pathname);\n span.setAttribute(ATTR_URL_FULL, window.location.href);\n if (route) {\n span.setAttribute(ATTR_HTTP_ROUTE, route);\n }\n}\n\nexport interface InstrumentationsInput {\n /**\n * URLs the SDK exempts from `fetch` and `XHR` instrumentation —\n * typically our own ingest/OTLP endpoints, so the SDK doesn't trace\n * its own export requests in an infinite loop. Combined with the\n * `THIRD_PARTY_IGNORE` defaults and the customer's `ignoreUrls`.\n */\n ignoreUrls: (string | RegExp)[];\n /**\n * Customer-supplied URL patterns the fetch + XHR instrumentations\n * inject `traceparent` + `baggage` headers on. Same-origin requests\n * always propagate.\n */\n propagateContextUrls?: (string | RegExp)[];\n /**\n * Pathname → low-cardinality route template (e.g. `/blog/[slug]`).\n * Drives span renaming + `url.path` / `http.route` attrs across\n * fetch, document-load, user-interaction, and long-task.\n */\n resolveRoute?: (pathname: string) => string | undefined;\n /**\n * The kernel's private provider — instrumentations register against\n * it, not the global one, so customer OTel setups stay untouched.\n */\n tracerProvider: WebTracerProvider;\n}\n\n/**\n * Registers every browser auto-instrumentation against the kernel's\n * private provider. Every instrumentation is enriched with the same\n * route-template-aware URL attributes so dashboards can slice fetch /\n * interaction / long-task / resource-load by `http.route` without\n * cardinality blow-up from raw pathnames.\n *\n * Returns a disposer that unregisters everything; called by\n * `kernel.dispose()`.\n */\nexport function registerBundledInstrumentations(\n input: InstrumentationsInput\n): () => void {\n const { tracerProvider, ignoreUrls, propagateContextUrls = [] } = input;\n const resolveRoute = input.resolveRoute;\n const origin = sameOriginPattern();\n const fetchIgnore = [...ignoreUrls, ...THIRD_PARTY_IGNORE];\n\n return registerInstrumentations({\n tracerProvider,\n instrumentations: [\n new FetchInstrumentation({\n propagateTraceHeaderCorsUrls: [origin, ...propagateContextUrls],\n ignoreUrls: fetchIgnore,\n semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,\n measureRequestSize: true,\n applyCustomAttributesOnSpan: (\n span: Span,\n request: Request | RequestInit\n ) => {\n const url =\n request instanceof Request ? request.url : request?.toString();\n if (typeof url !== \"string\") {\n return;\n }\n try {\n const parsed = new URL(url);\n span.setAttribute(ATTR_URL_PATH, parsed.pathname);\n const isTracked =\n origin.test(url) ||\n propagateContextUrls.some((re) =>\n re instanceof RegExp ? re.test(url) : url.includes(re)\n );\n if (isTracked) {\n const method =\n (request instanceof Request ? request.method : undefined) ??\n \"GET\";\n const route = resolveRoute?.(parsed.pathname);\n if (route) {\n span.setAttribute(ATTR_HTTP_ROUTE, route);\n }\n span.updateName(`${method} ${route ?? parsed.pathname}`);\n }\n } catch {\n return;\n }\n },\n }),\n new XMLHttpRequestInstrumentation({\n propagateTraceHeaderCorsUrls: [origin, ...propagateContextUrls],\n ignoreUrls: fetchIgnore,\n semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,\n }),\n new DocumentLoadInstrumentation({\n semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,\n // Default `resourceFetch` span name is the literal string\n // `\"resourceFetch\"` for every entry — useless when a single\n // page load emits 30 of them. Replace with the pathname (or\n // origin+path for cross-origin) so the waterfall is scannable.\n applyCustomAttributesOnSpan: {\n resourceFetch: (span, entry) => {\n const name = entry?.name;\n if (typeof name !== \"string\") {\n return;\n }\n try {\n const url = new URL(name);\n const isCrossOrigin =\n typeof window !== \"undefined\" &&\n url.origin !== window.location.origin;\n span.updateName(\n isCrossOrigin\n ? `resource: ${url.host}${url.pathname}`\n : `resource: ${url.pathname}`\n );\n span.setAttribute(ATTR_URL_FULL, name);\n span.setAttribute(ATTR_URL_PATH, url.pathname);\n span.setAttribute(\"server.address\", url.host);\n } catch {\n // Malformed/relative URL — leave the default name and\n // let the entry's own attributes carry whatever info\n // the SDK already stamped.\n }\n },\n },\n }),\n // `BrowserNavigationInstrumentation` emits a `browser.navigation`\n // log record per hard navigation (page load) and soft navigation\n // (history.pushState, hash change, back/forward). The default\n // sanitizer redacts credentials in the URL and common sensitive\n // query params (`token`, `api_key`, `secret`, …) before emit.\n new BrowserNavigationInstrumentation({\n sanitizeUrl: defaultSanitizeUrl,\n }),\n new UserInteractionInstrumentation({\n eventNames: [\"click\", \"submit\"],\n // Despite the name, `shouldPreventSpanCreation` is also the\n // hook for *enriching* spans (return falsy → create the span;\n // the callback may set attributes on the way through). We use\n // it as both: stamp a stable target descriptor for\n // dashboarding, then suppress spans on elements that aren't\n // actionable (scroll containers, body clicks, etc.).\n shouldPreventSpanCreation: (eventType, element, span) => {\n if (eventType === \"click\" && !isActionable(element)) {\n return true;\n }\n stampInteractionAttrs(eventType, element, span);\n stampUrlAttrs(span, resolveRoute);\n return false;\n },\n }),\n // Long-task spans default to a generic `longtask` name with\n // attribution embedded as attributes — but no page context.\n // Empty `url.path` makes it impossible to triage which routes\n // are jank-heavy. Stamp it in the observer callback so\n // dashboards can slice by route.\n new LongTaskInstrumentation({\n observerCallback: (span, info: ObserverCallbackInformation) => {\n stampUrlAttrs(span, resolveRoute);\n // PerformanceLongTaskTiming attribution[0].containerType is\n // the surface that hosted the offending task. Most are\n // `\"window\"` (main frame); the few that aren't (`\"iframe\"`,\n // `\"embed\"`, `\"object\"`) are the actionable ones — surface\n // them as a top-level attr instead of leaving them buried\n // in `longtask.attribution.container_type`.\n const containerType =\n info.longtaskEntry.attribution[0]?.containerType;\n if (containerType) {\n span.setAttribute(\"longtask.container_type\", containerType);\n }\n },\n }),\n ],\n });\n}\n"],"mappings":";;;;;;;;;AAkBA,MAAM,kBAAkB;AACxB,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;;;;;;;;AAStB,MAAM,2BACJ;;;;;;;;;;AAWF,MAAM,qBAA+B;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAED,MAAM,cAAc;AACpB,MAAM,YAAY;AAElB,SAAS,oBAA4B;CACnC,IAAI,OAAO,WAAW,aACpB,OAAO;CAET,MAAM,EAAE,UAAU,SAAS,OAAO;CAClC,OAAO,IAAI,OAAO,IAAI,SAAS,IAAI,KAAK,QAAQ,WAAW,OAAO,GAAG;;AAGvE,SAAS,sBACP,WACA,SACA,MACM;CACN,MAAM,OAAO,mBAAmB,QAAQ;CACxC,KAAK,aAAa,iBAAiB,UAAU;CAC7C,KAAK,aAAa,iBAAiB,KAAK,IAAI;CAC5C,IAAI,KAAK,IACP,KAAK,aAAa,gBAAgB,KAAK,GAAG;CAE5C,IAAI,KAAK,MACP,KAAK,aAAa,kBAAkB,KAAK,KAAK;CAEhD,IAAI,KAAK,MACP,KAAK,aAAa,kBAAkB,KAAK,KAAK;CAEhD,IAAI,KAAK,WACP,KAAK,aAAa,wBAAwB,KAAK,UAAU;CAI3D,IAAI,KAAK,MACP,KAAK,aAAa,kBAAkB,KAAK,KAAK;;AAIlD,SAAS,cACP,MACA,cACM;CACN,IAAI,OAAO,WAAW,aACpB;CAEF,MAAM,WAAW,OAAO,SAAS;CACjC,MAAM,QAAQ,eAAe,SAAS;CACtC,KAAK,aAAa,eAAe,SAAS,SAAS;CACnD,KAAK,aAAa,eAAe,OAAO,SAAS,KAAK;CACtD,IAAI,OACF,KAAK,aAAa,iBAAiB,MAAM;;;;;;;;;;;;AAyC7C,SAAgB,gCACd,OACY;CACZ,MAAM,EAAE,gBAAgB,YAAY,uBAAuB,EAAE,KAAK;CAClE,MAAM,eAAe,MAAM;CAC3B,MAAM,SAAS,mBAAmB;CAClC,MAAM,cAAc,CAAC,GAAG,YAAY,GAAG,mBAAmB;CAE1D,OAAO,yBAAyB;EAC9B;EACA,kBAAkB;GAChB,IAAI,qBAAqB;IACvB,8BAA8B,CAAC,QAAQ,GAAG,qBAAqB;IAC/D,YAAY;IACZ,uBAAuB;IACvB,oBAAoB;IACpB,8BACE,MACA,YACG;KACH,MAAM,MACJ,mBAAmB,UAAU,QAAQ,MAAM,SAAS,UAAU;KAChE,IAAI,OAAO,QAAQ,UACjB;KAEF,IAAI;MACF,MAAM,SAAS,IAAI,IAAI,IAAI;MAC3B,KAAK,aAAa,eAAe,OAAO,SAAS;MAMjD,IAJE,OAAO,KAAK,IAAI,IAChB,qBAAqB,MAAM,OACzB,cAAc,SAAS,GAAG,KAAK,IAAI,GAAG,IAAI,SAAS,GAAG,CACvD,EACY;OACb,MAAM,UACH,mBAAmB,UAAU,QAAQ,SAAS,KAAA,MAC/C;OACF,MAAM,QAAQ,eAAe,OAAO,SAAS;OAC7C,IAAI,OACF,KAAK,aAAa,iBAAiB,MAAM;OAE3C,KAAK,WAAW,GAAG,OAAO,GAAG,SAAS,OAAO,WAAW;;aAEpD;MACN;;;IAGL,CAAC;GACF,IAAI,8BAA8B;IAChC,8BAA8B,CAAC,QAAQ,GAAG,qBAAqB;IAC/D,YAAY;IACZ,uBAAuB;IACxB,CAAC;GACF,IAAI,4BAA4B;IAC9B,uBAAuB;IAKvB,6BAA6B,EAC3B,gBAAgB,MAAM,UAAU;KAC9B,MAAM,OAAO,OAAO;KACpB,IAAI,OAAO,SAAS,UAClB;KAEF,IAAI;MACF,MAAM,MAAM,IAAI,IAAI,KAAK;MACzB,MAAM,gBACJ,OAAO,WAAW,eAClB,IAAI,WAAW,OAAO,SAAS;MACjC,KAAK,WACH,gBACI,aAAa,IAAI,OAAO,IAAI,aAC5B,aAAa,IAAI,WACtB;MACD,KAAK,aAAa,eAAe,KAAK;MACtC,KAAK,aAAa,eAAe,IAAI,SAAS;MAC9C,KAAK,aAAa,kBAAkB,IAAI,KAAK;aACvC;OAMX;IACF,CAAC;GAMF,IAAI,iCAAiC,EACnC,aAAa,oBACd,CAAC;GACF,IAAI,+BAA+B;IACjC,YAAY,CAAC,SAAS,SAAS;IAO/B,4BAA4B,WAAW,SAAS,SAAS;KACvD,IAAI,cAAc,WAAW,CAAC,aAAa,QAAQ,EACjD,OAAO;KAET,sBAAsB,WAAW,SAAS,KAAK;KAC/C,cAAc,MAAM,aAAa;KACjC,OAAO;;IAEV,CAAC;GAMF,IAAI,wBAAwB,EAC1B,mBAAmB,MAAM,SAAsC;IAC7D,cAAc,MAAM,aAAa;IAOjC,MAAM,gBACJ,KAAK,cAAc,YAAY,IAAI;IACrC,IAAI,eACF,KAAK,aAAa,2BAA2B,cAAc;MAGhE,CAAC;GACH;EACF,CAAC"}
@@ -0,0 +1,32 @@
1
+ import { Context } from "@opentelemetry/api";
2
+ import { ZoneContextManager } from "@opentelemetry/context-zone";
3
+
4
+ //#region src/internal/otel/page-scope-context-manager.d.ts
5
+ /**
6
+ * `ZoneContextManager` returns `ROOT_CONTEXT` whenever `context.active()` is
7
+ * called outside a zone the manager itself created — which in practice is
8
+ * most of the time:
9
+ *
10
+ * - app code calling `fetch(...)` from a `useEffect`
11
+ * - `PerformanceObserver` callbacks (long-task, paint timing, …)
12
+ * - top-level await / module init code
13
+ * - listeners attached before the SDK booted
14
+ *
15
+ * Spans created from those code paths default to "no parent" → root, which
16
+ * is why a single page load produces N disjoint traces (one per
17
+ * instrumentation) instead of one waterfall.
18
+ *
19
+ * This subclass adds a single fallback: if the underlying zone lookup yields
20
+ * `ROOT_CONTEXT`, return the page-scope context instead. The page-scope
21
+ * context is built once at SDK init from the `<meta name="traceparent">`
22
+ * tag emitted by the SSR layout. If the meta tag is absent the page scope
23
+ * stays at `ROOT_CONTEXT` and the manager behaves like a stock
24
+ * `ZoneContextManager`.
25
+ */
26
+ declare class PageScopeContextManager extends ZoneContextManager {
27
+ private pageScope;
28
+ setPageScope(ctx: Context): void;
29
+ active(): Context;
30
+ }
31
+ //#endregion
32
+ export { PageScopeContextManager };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-scope-context-manager.d.mts","names":[],"sources":["../../../src/internal/otel/page-scope-context-manager.ts"],"mappings":";;;;;;AAyBA;;;;;;;;;;;;;;;;;;;cAAa,uBAAA,SAAgC,kBAAA;EAAA,QACnC,SAAA;EAER,YAAA,CAAa,GAAA,EAAK,OAAA;EAIT,MAAA,CAAA,GAAU,OAAA;AAAA"}
@@ -0,0 +1,36 @@
1
+ import { ROOT_CONTEXT } from "@opentelemetry/api";
2
+ import { ZoneContextManager } from "@opentelemetry/context-zone";
3
+ //#region src/internal/otel/page-scope-context-manager.ts
4
+ /**
5
+ * `ZoneContextManager` returns `ROOT_CONTEXT` whenever `context.active()` is
6
+ * called outside a zone the manager itself created — which in practice is
7
+ * most of the time:
8
+ *
9
+ * - app code calling `fetch(...)` from a `useEffect`
10
+ * - `PerformanceObserver` callbacks (long-task, paint timing, …)
11
+ * - top-level await / module init code
12
+ * - listeners attached before the SDK booted
13
+ *
14
+ * Spans created from those code paths default to "no parent" → root, which
15
+ * is why a single page load produces N disjoint traces (one per
16
+ * instrumentation) instead of one waterfall.
17
+ *
18
+ * This subclass adds a single fallback: if the underlying zone lookup yields
19
+ * `ROOT_CONTEXT`, return the page-scope context instead. The page-scope
20
+ * context is built once at SDK init from the `<meta name="traceparent">`
21
+ * tag emitted by the SSR layout. If the meta tag is absent the page scope
22
+ * stays at `ROOT_CONTEXT` and the manager behaves like a stock
23
+ * `ZoneContextManager`.
24
+ */
25
+ var PageScopeContextManager = class extends ZoneContextManager {
26
+ pageScope = ROOT_CONTEXT;
27
+ setPageScope(ctx) {
28
+ this.pageScope = ctx;
29
+ }
30
+ active() {
31
+ const ctx = super.active();
32
+ return ctx === ROOT_CONTEXT ? this.pageScope : ctx;
33
+ }
34
+ };
35
+ //#endregion
36
+ export { PageScopeContextManager };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-scope-context-manager.mjs","names":[],"sources":["../../../src/internal/otel/page-scope-context-manager.ts"],"sourcesContent":["import type { Context } from \"@opentelemetry/api\";\nimport { ROOT_CONTEXT } from \"@opentelemetry/api\";\nimport { ZoneContextManager } from \"@opentelemetry/context-zone\";\n\n/**\n * `ZoneContextManager` returns `ROOT_CONTEXT` whenever `context.active()` is\n * called outside a zone the manager itself created — which in practice is\n * most of the time:\n *\n * - app code calling `fetch(...)` from a `useEffect`\n * - `PerformanceObserver` callbacks (long-task, paint timing, …)\n * - top-level await / module init code\n * - listeners attached before the SDK booted\n *\n * Spans created from those code paths default to \"no parent\" → root, which\n * is why a single page load produces N disjoint traces (one per\n * instrumentation) instead of one waterfall.\n *\n * This subclass adds a single fallback: if the underlying zone lookup yields\n * `ROOT_CONTEXT`, return the page-scope context instead. The page-scope\n * context is built once at SDK init from the `<meta name=\"traceparent\">`\n * tag emitted by the SSR layout. If the meta tag is absent the page scope\n * stays at `ROOT_CONTEXT` and the manager behaves like a stock\n * `ZoneContextManager`.\n */\nexport class PageScopeContextManager extends ZoneContextManager {\n private pageScope: Context = ROOT_CONTEXT;\n\n setPageScope(ctx: Context): void {\n this.pageScope = ctx;\n }\n\n override active(): Context {\n const ctx = super.active();\n return ctx === ROOT_CONTEXT ? this.pageScope : ctx;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAyBA,IAAa,0BAAb,cAA6C,mBAAmB;CAC9D,YAA6B;CAE7B,aAAa,KAAoB;EAC/B,KAAK,YAAY;;CAGnB,SAA2B;EACzB,MAAM,MAAM,MAAM,QAAQ;EAC1B,OAAO,QAAQ,eAAe,KAAK,YAAY"}
@@ -0,0 +1,21 @@
1
+ import { Context } from "@opentelemetry/api";
2
+
3
+ //#region src/internal/otel/propagation.d.ts
4
+ /**
5
+ * Reads the W3C `traceparent` (and optional `tracestate`) meta tag from the
6
+ * document head and extracts an OTel `Context`. SSR renderers (Next, Vite,
7
+ * …) inject the server-side request span's context so the client SDK can
8
+ * stitch its spans onto the same trace.
9
+ *
10
+ * Returns `null` when:
11
+ * - called server-side (no `document`)
12
+ * - no `traceparent` meta tag is present
13
+ * - the extracted context has an invalid trace id
14
+ * - the `sampled` flag is unset — the browser doesn't honor the bit when
15
+ * creating child spans, so an unsampled parent would orphan every
16
+ * browser span under a trace with no recorded server segments. Drop
17
+ * instead so spans fall back to a fresh root.
18
+ */
19
+ declare function readPropagationFromDocument(): Context | null;
20
+ //#endregion
21
+ export { readPropagationFromDocument };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"propagation.d.mts","names":[],"sources":["../../../src/internal/otel/propagation.ts"],"mappings":";;;;;AAuCA;;;;;;;;;;;;;iBAAgB,2BAAA,CAAA,GAA+B,OAAA"}
@@ -0,0 +1,40 @@
1
+ import { ROOT_CONTEXT, defaultTextMapGetter, trace } from "@opentelemetry/api";
2
+ import { W3CTraceContextPropagator } from "@opentelemetry/core";
3
+ //#region src/internal/otel/propagation.ts
4
+ const TRACEPARENT_META = "traceparent";
5
+ const TRACESTATE_META = "tracestate";
6
+ const INVALID_TRACE_ID = "00000000000000000000000000000000";
7
+ const TRACE_FLAGS_SAMPLED = 1;
8
+ const W3C_PROPAGATOR = new W3CTraceContextPropagator();
9
+ function readMeta(name) {
10
+ if (typeof document === "undefined") return null;
11
+ return document.querySelector(`meta[name="${name}"]`)?.getAttribute("content") ?? null;
12
+ }
13
+ /**
14
+ * Reads the W3C `traceparent` (and optional `tracestate`) meta tag from the
15
+ * document head and extracts an OTel `Context`. SSR renderers (Next, Vite,
16
+ * …) inject the server-side request span's context so the client SDK can
17
+ * stitch its spans onto the same trace.
18
+ *
19
+ * Returns `null` when:
20
+ * - called server-side (no `document`)
21
+ * - no `traceparent` meta tag is present
22
+ * - the extracted context has an invalid trace id
23
+ * - the `sampled` flag is unset — the browser doesn't honor the bit when
24
+ * creating child spans, so an unsampled parent would orphan every
25
+ * browser span under a trace with no recorded server segments. Drop
26
+ * instead so spans fall back to a fresh root.
27
+ */
28
+ function readPropagationFromDocument() {
29
+ const traceparent = readMeta(TRACEPARENT_META);
30
+ if (!traceparent) return null;
31
+ const carrier = { traceparent };
32
+ const tracestate = readMeta(TRACESTATE_META);
33
+ if (tracestate) carrier[TRACESTATE_META] = tracestate;
34
+ const extracted = W3C_PROPAGATOR.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
35
+ const spanCtx = trace.getSpanContext(extracted);
36
+ if (!spanCtx || spanCtx.traceId === INVALID_TRACE_ID || (spanCtx.traceFlags & TRACE_FLAGS_SAMPLED) === 0) return null;
37
+ return extracted;
38
+ }
39
+ //#endregion
40
+ export { readPropagationFromDocument };