@interfere/react 10.0.1-canary.1 → 11.0.0-canary.2

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 (83) hide show
  1. package/README.md +1 -1
  2. package/dist/api.d.mts.map +1 -1
  3. package/dist/error-boundary.d.mts.map +1 -1
  4. package/dist/internal/browser-context.d.mts.map +1 -1
  5. package/dist/internal/browser-context.mjs.map +1 -1
  6. package/dist/internal/capture-boundary.d.mts.map +1 -1
  7. package/dist/internal/capture-boundary.mjs.map +1 -1
  8. package/dist/internal/config.d.mts.map +1 -1
  9. package/dist/internal/config.mjs.map +1 -1
  10. package/dist/internal/console-patch.d.mts.map +1 -1
  11. package/dist/internal/kernel-registry.d.mts.map +1 -1
  12. package/dist/internal/kernel.d.mts +10 -2
  13. package/dist/internal/kernel.d.mts.map +1 -1
  14. package/dist/internal/kernel.mjs +1 -1
  15. package/dist/internal/kernel.mjs.map +1 -1
  16. package/dist/internal/otel/exporter.d.mts.map +1 -1
  17. package/dist/internal/otel/exporter.mjs.map +1 -1
  18. package/dist/internal/otel/index.d.mts +2 -2
  19. package/dist/internal/otel/instrumentations.d.mts +24 -1
  20. package/dist/internal/otel/instrumentations.d.mts.map +1 -1
  21. package/dist/internal/otel/instrumentations.mjs +1 -1
  22. package/dist/internal/otel/instrumentations.mjs.map +1 -1
  23. package/dist/internal/otel/page-scope-context-manager.d.mts.map +1 -1
  24. package/dist/internal/otel/propagation.d.mts.map +1 -1
  25. package/dist/internal/otel/propagation.mjs.map +1 -1
  26. package/dist/internal/otel/provider.d.mts.map +1 -1
  27. package/dist/internal/otel/provider.mjs +1 -1
  28. package/dist/internal/otel/provider.mjs.map +1 -1
  29. package/dist/internal/page-lifecycle.d.mts +2 -15
  30. package/dist/internal/page-lifecycle.d.mts.map +1 -1
  31. package/dist/internal/page-lifecycle.mjs +1 -1
  32. package/dist/internal/page-lifecycle.mjs.map +1 -1
  33. package/dist/internal/plugin-runtime.d.mts.map +1 -1
  34. package/dist/internal/sw.mjs.map +1 -1
  35. package/dist/package.mjs +1 -1
  36. package/dist/plugins/errors.d.mts.map +1 -1
  37. package/dist/plugins/errors.mjs.map +1 -1
  38. package/dist/plugins/lib/loader.d.mts.map +1 -1
  39. package/dist/plugins/lib/loader.mjs +1 -1
  40. package/dist/plugins/lib/loader.mjs.map +1 -1
  41. package/dist/plugins/rage-clicks.d.mts.map +1 -1
  42. package/dist/plugins/rage-clicks.mjs.map +1 -1
  43. package/dist/plugins/replay/buffer.d.mts +13 -0
  44. package/dist/plugins/replay/buffer.d.mts.map +1 -0
  45. package/dist/plugins/replay/buffer.mjs +1 -0
  46. package/dist/plugins/replay/buffer.mjs.map +1 -0
  47. package/dist/plugins/replay/chunking.d.mts +21 -0
  48. package/dist/plugins/replay/chunking.d.mts.map +1 -0
  49. package/dist/plugins/replay/chunking.mjs +1 -0
  50. package/dist/plugins/replay/chunking.mjs.map +1 -0
  51. package/dist/plugins/replay/compressor-host.d.mts +9 -0
  52. package/dist/plugins/replay/compressor-host.d.mts.map +1 -0
  53. package/dist/plugins/replay/compressor-host.mjs +1 -0
  54. package/dist/plugins/replay/compressor-host.mjs.map +1 -0
  55. package/dist/plugins/replay/delivery.d.mts +23 -0
  56. package/dist/plugins/replay/delivery.d.mts.map +1 -0
  57. package/dist/plugins/replay/delivery.mjs +1 -0
  58. package/dist/plugins/replay/delivery.mjs.map +1 -0
  59. package/dist/plugins/replay/index.d.mts +6 -0
  60. package/dist/plugins/replay/index.d.mts.map +1 -0
  61. package/dist/plugins/replay/index.mjs +1 -0
  62. package/dist/plugins/replay/index.mjs.map +1 -0
  63. package/dist/plugins/replay/recorder.d.mts +8 -0
  64. package/dist/plugins/replay/recorder.d.mts.map +1 -0
  65. package/dist/plugins/replay/recorder.mjs +1 -0
  66. package/dist/plugins/replay/recorder.mjs.map +1 -0
  67. package/dist/provider.d.mts.map +1 -1
  68. package/dist/sw.mjs +1 -1
  69. package/dist/tracking/api.d.mts.map +1 -1
  70. package/dist/tracking/api.mjs +1 -1
  71. package/dist/tracking/api.mjs.map +1 -1
  72. package/dist/tracking/device.d.mts.map +1 -1
  73. package/dist/tracking/device.mjs.map +1 -1
  74. package/dist/tracking/geo.d.mts.map +1 -1
  75. package/dist/tracking/session.d.mts.map +1 -1
  76. package/dist/util/bot.d.mts.map +1 -1
  77. package/dist/util/global.d.mts.map +1 -1
  78. package/dist/util/log.d.mts.map +1 -1
  79. package/package.json +15 -14
  80. package/dist/plugins/replay.d.mts +0 -6
  81. package/dist/plugins/replay.d.mts.map +0 -1
  82. package/dist/plugins/replay.mjs +0 -1
  83. package/dist/plugins/replay.mjs.map +0 -1
@@ -1 +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 (feature-flag 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":"6pBAkBA,MAAM,gBAAkB,aAClB,cAAgB,WAChB,cAAgB,WAShB,yBACJ,qDAWI,mBAA+B,CACnC,oBACA,YACA,aACA,eACA,cACA,0BACA,0BACA,oBACA,qBACA,kBACA,eACA,2BACA,yBACA,WACF,EAEM,YAAc,KACd,UAAY,sBAElB,SAAS,mBAA4B,CACnC,GAAI,OAAO,OAAW,IACpB,OAAO,YAET,GAAM,CAAE,SAAU,MAAS,OAAO,SAClC,OAAW,OAAO,IAAI,SAAS,IAAI,KAAK,QAAQ,UAAW,MAAM,GAAG,CACtE,CAEA,SAAS,sBACP,UACA,QACA,KACM,CACN,IAAM,KAAO,mBAAmB,OAAO,EACvC,KAAK,aAAa,gBAAiB,SAAS,EAC5C,KAAK,aAAa,gBAAiB,KAAK,GAAG,EACvC,KAAK,IACP,KAAK,aAAa,eAAgB,KAAK,EAAE,EAEvC,KAAK,MACP,KAAK,aAAa,iBAAkB,KAAK,IAAI,EAE3C,KAAK,MACP,KAAK,aAAa,iBAAkB,KAAK,IAAI,EAE3C,KAAK,WACP,KAAK,aAAa,uBAAwB,KAAK,SAAS,EAItD,KAAK,MACP,KAAK,aAAa,iBAAkB,KAAK,IAAI,CAEjD,CAEA,SAAS,cACP,KACA,aACM,CACN,GAAI,OAAO,OAAW,IACpB,OAEF,IAAM,SAAW,OAAO,SAAS,SAC3B,MAAQ,eAAe,QAAQ,EACrC,KAAK,aAAa,cAAe,OAAS,QAAQ,EAClD,KAAK,aAAa,cAAe,OAAO,SAAS,IAAI,EACjD,OACF,KAAK,aAAa,gBAAiB,KAAK,CAE5C,CAuCA,SAAgB,gCACd,MACY,CACZ,GAAM,CAAE,eAAgB,WAAY,qBAAuB,CAAC,GAAM,MAC5D,aAAe,MAAM,aACrB,OAAS,kBAAkB,EAC3B,YAAc,CAAC,GAAG,WAAY,GAAG,kBAAkB,EAEzD,OAAO,yBAAyB,CAC9B,eACA,iBAAkB,CAChB,IAAI,qBAAqB,CACvB,6BAA8B,CAAC,OAAQ,GAAG,oBAAoB,EAC9D,WAAY,YACZ,sBAAuB,yBACvB,mBAAoB,GACpB,6BACE,KACA,UACG,CACH,IAAM,IACJ,mBAAmB,QAAU,QAAQ,IAAM,SAAS,SAAS,EAC3D,UAAO,KAAQ,SAGnB,GAAI,CACF,IAAM,OAAS,IAAI,IAAI,GAAG,EAO1B,GANA,KAAK,aAAa,cAAe,OAAO,QAAQ,EAE9C,OAAO,KAAK,GAAG,GACf,qBAAqB,KAAM,IACzB,cAAc,OAAS,GAAG,KAAK,GAAG,EAAI,IAAI,SAAS,EAAE,CACvD,EACa,CACb,IAAM,QACH,mBAAmB,QAAU,QAAQ,OAAS,IAAA,KAC/C,MACI,MAAQ,eAAe,OAAO,QAAQ,EACxC,OACF,KAAK,aAAa,gBAAiB,KAAK,EAE1C,KAAK,WAAW,GAAG,OAAO,GAAG,OAAS,OAAO,UAAU,CACzD,CACF,MAAQ,CACN,MACF,CACF,CACF,CAAC,EACD,IAAI,8BAA8B,CAChC,6BAA8B,CAAC,OAAQ,GAAG,oBAAoB,EAC9D,WAAY,YACZ,sBAAuB,wBACzB,CAAC,EACD,IAAI,4BAA4B,CAC9B,sBAAuB,yBAKvB,4BAA6B,CAC3B,eAAgB,KAAM,QAAU,CAC9B,IAAM,KAAO,OAAO,KAChB,UAAO,MAAS,SAGpB,GAAI,CACF,IAAM,IAAM,IAAI,IAAI,IAAI,EAClB,cACJ,OAAO,OAAW,KAClB,IAAI,SAAW,OAAO,SAAS,OACjC,KAAK,WACH,cACI,aAAa,IAAI,OAAO,IAAI,WAC5B,aAAa,IAAI,UACvB,EACA,KAAK,aAAa,cAAe,IAAI,EACrC,KAAK,aAAa,cAAe,IAAI,QAAQ,EAC7C,KAAK,aAAa,iBAAkB,IAAI,IAAI,CAC9C,MAAQ,CAIR,CACF,CACF,CACF,CAAC,EAMD,IAAI,iCAAiC,CACnC,YAAa,kBACf,CAAC,EACD,IAAI,+BAA+B,CACjC,WAAY,CAAC,QAAS,QAAQ,EAO9B,2BAA4B,UAAW,QAAS,OAC1C,YAAc,SAAW,CAAC,aAAa,OAAO,EACzC,IAET,sBAAsB,UAAW,QAAS,IAAI,EAC9C,cAAc,KAAM,YAAY,EACzB,GAEX,CAAC,EAMD,IAAI,wBAAwB,CAC1B,kBAAmB,KAAM,OAAsC,CAC7D,cAAc,KAAM,YAAY,EAOhC,IAAM,cACJ,KAAK,cAAc,YAAY,IAAI,cACjC,eACF,KAAK,aAAa,0BAA2B,aAAa,CAE9D,CACF,CAAC,CACH,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"instrumentations.mjs","names":[],"sources":["../../../src/internal/otel/instrumentations.ts"],"sourcesContent":["import { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport 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 { LoggerProvider } from \"@opentelemetry/sdk-logs\";\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\ninterface FetchErrorShape {\n message: string;\n status?: number;\n}\n\ntype FetchSpanRequest = Request | RequestInfo | RequestInit | URL;\n\n/**\n * Inclusive HTTP status range. `[400, 499]` matches every 4xx;\n * a bare number matches that exact code.\n */\nexport type HttpStatusCodeRange = number | readonly [number, number];\n\nexport interface CaptureFailedRequestsOptions {\n /** Defaults to `[[500, 599]]` when `captureFailedRequests` is `true`. */\n statusCodes?: readonly HttpStatusCodeRange[];\n}\n\n/** Promote tracked failed fetch/XHR responses to captured exception events. */\nexport type CaptureFailedRequestsOption =\n | boolean\n | CaptureFailedRequestsOptions;\n\nconst DEFAULT_FAILURE_RANGES: readonly HttpStatusCodeRange[] = [[500, 599]];\n\nfunction resolveFailureRanges(\n opt: CaptureFailedRequestsOption | undefined\n): readonly HttpStatusCodeRange[] | null {\n if (!opt) {\n return null;\n }\n if (opt === true) {\n return DEFAULT_FAILURE_RANGES;\n }\n const codes = opt.statusCodes;\n return codes && codes.length > 0 ? codes : DEFAULT_FAILURE_RANGES;\n}\n\nfunction statusMatches(\n status: number,\n ranges: readonly HttpStatusCodeRange[]\n): boolean {\n for (const range of ranges) {\n if (typeof range === \"number\") {\n if (status === range) {\n return true;\n }\n } else if (status >= range[0] && status <= range[1]) {\n return true;\n }\n }\n return false;\n}\n\nfunction recordHttpFailure(\n span: Span,\n status: number,\n method: string,\n url: string,\n mechanism: string\n): void {\n span.addEvent(\"exception\", {\n \"exception.type\": \"HttpResponseError\",\n \"exception.message\": `HTTP ${status} ${method} ${url}`,\n \"interfere.exception.mechanism\": mechanism,\n \"interfere.exception.handled\": false,\n \"interfere.exception.kind\": \"error\",\n });\n}\n\nfunction maybeCaptureFetchFailure(\n span: Span,\n result: Response | FetchErrorShape,\n failureRanges: readonly HttpStatusCodeRange[] | null,\n method: string,\n url: string\n): void {\n if (!failureRanges) {\n return;\n }\n const status = result?.status;\n if (\n typeof status !== \"number\" ||\n status <= 0 ||\n !statusMatches(status, failureRanges)\n ) {\n return;\n }\n recordHttpFailure(\n span,\n status,\n method,\n url,\n MECHANISM_TYPE.browser.fetchFailedResponse\n );\n}\n\nfunction applyFetchSpanAttrs(\n span: Span,\n request: FetchSpanRequest,\n result: Response | FetchErrorShape,\n failureRanges: readonly HttpStatusCodeRange[] | null,\n isTrackedUrl: (url: string) => boolean,\n resolveRoute: ((pathname: string) => string | undefined) | undefined\n): void {\n const url = resolveFetchUrl(request, result);\n if (!url) {\n return;\n }\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n return;\n }\n span.setAttribute(ATTR_URL_PATH, parsed.pathname);\n const tracked = isTrackedUrl(url);\n const method = resolveFetchMethod(request);\n if (tracked) {\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 maybeCaptureFetchFailure(span, result, failureRanges, method, url);\n }\n}\n\nfunction resolveFetchUrl(\n request: FetchSpanRequest,\n result: Response | FetchErrorShape\n): string | null {\n return responseUrl(result) ?? requestUrl(request);\n}\n\nfunction responseUrl(result: Response | FetchErrorShape): string | null {\n return result instanceof Response && result.url ? result.url : null;\n}\n\nfunction requestUrl(request: FetchSpanRequest): string | null {\n if (typeof request === \"string\") {\n return request;\n }\n return readStringProp(request, \"url\") ?? readStringProp(request, \"href\");\n}\n\nfunction resolveFetchMethod(request: FetchSpanRequest): string {\n return readStringProp(request, \"method\") ?? \"GET\";\n}\n\nfunction readStringProp(value: unknown, key: string): string | null {\n if (!(value && typeof value === \"object\" && key in value)) {\n return null;\n }\n const prop = value[key as keyof typeof value];\n return typeof prop === \"string\" ? prop : null;\n}\n\nexport const _testOnlyApplyFetchSpanAttrs = applyFetchSpanAttrs;\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 (feature-flag 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\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 /** See {@link CaptureFailedRequestsOption}. Off by default. */\n captureFailedRequests?: CaptureFailedRequestsOption;\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 loggerProvider?: LoggerProvider;\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 {\n tracerProvider,\n loggerProvider,\n ignoreUrls,\n propagateContextUrls = [],\n } = input;\n const resolveRoute = input.resolveRoute;\n const origin = sameOriginPattern();\n const fetchIgnore = [...ignoreUrls, ...THIRD_PARTY_IGNORE];\n const failureRanges = resolveFailureRanges(input.captureFailedRequests);\n\n function isTrackedUrl(url: string): boolean {\n return (\n origin.test(url) ||\n propagateContextUrls.some((re) =>\n re instanceof RegExp ? re.test(url) : url.includes(re)\n )\n );\n }\n\n return registerInstrumentations({\n tracerProvider,\n ...(loggerProvider ? { loggerProvider } : {}),\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 result: Response | FetchErrorShape\n ) => {\n applyFetchSpanAttrs(\n span,\n request,\n result,\n failureRanges,\n isTrackedUrl,\n resolveRoute\n );\n },\n }),\n new XMLHttpRequestInstrumentation({\n propagateTraceHeaderCorsUrls: [origin, ...propagateContextUrls],\n ignoreUrls: fetchIgnore,\n semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,\n applyCustomAttributesOnSpan: (span: Span, xhr: XMLHttpRequest) => {\n if (!failureRanges) {\n return;\n }\n const url = xhr.responseURL;\n if (!(url && isTrackedUrl(url))) {\n return;\n }\n const status = xhr.status;\n if (typeof status !== \"number\" || status === 0) {\n return;\n }\n if (!statusMatches(status, failureRanges)) {\n return;\n }\n\n recordHttpFailure(\n span,\n status,\n \"XHR\",\n url,\n MECHANISM_TYPE.browser.xhrFailedResponse\n );\n },\n }),\n new DocumentLoadInstrumentation({\n semconvStabilityOptIn: SEMCONV_STABILITY_OPT_IN,\n\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 return;\n }\n },\n },\n }),\n\n new BrowserNavigationInstrumentation({\n sanitizeUrl: defaultSanitizeUrl,\n }),\n new UserInteractionInstrumentation({\n eventNames: [\"click\", \"submit\"],\n\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\n new LongTaskInstrumentation({\n observerCallback: (span, info: ObserverCallbackInformation) => {\n stampUrlAttrs(span, resolveRoute);\n\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":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"page-scope-context-manager.d.mts","names":[],"sources":["../../../src/internal/otel/page-scope-context-manager.ts"],"mappings":";;;;;;AAyBA;;;;;;;;;;;;;;;;AAO4B;;;cAPf,uBAAA,SAAgC,kBAAA;EAAA,QACnC,SAAA;EAER,YAAA,CAAa,GAAA,EAAK,OAAA;EAIT,MAAA,CAAA,GAAU,OAAA;AAAA"}
1
+ {"version":3,"file":"page-scope-context-manager.d.mts","names":[],"sources":["../../../src/internal/otel/page-scope-context-manager.ts"],"mappings":";;;;;;AAyBA;;;;;;;;;;;;;;;;AAO4B;;;cAPf,uBAAA,SAAgC,kBAAA;EAAA,QACnC,SAAA;EAER,YAAA,CAAa,GAAA,EAAK,OAAA;EAIT,MAAA,IAAU,OAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"propagation.d.mts","names":[],"sources":["../../../src/internal/otel/propagation.ts"],"mappings":";;;;;AAuCA;;;;AAAsD;;;;;;;;;iBAAtC,2BAAA,CAAA,GAA+B,OAAO"}
1
+ {"version":3,"file":"propagation.d.mts","names":[],"sources":["../../../src/internal/otel/propagation.ts"],"mappings":";;;;;AAsCA;;;;AAAsD;;;;;;;;;iBAAtC,2BAAA,IAA+B,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"propagation.mjs","names":[],"sources":["../../../src/internal/otel/propagation.ts"],"sourcesContent":["import {\n type Context,\n defaultTextMapGetter,\n ROOT_CONTEXT,\n trace,\n} from \"@opentelemetry/api\";\nimport { W3CTraceContextPropagator } from \"@opentelemetry/core\";\n\nconst TRACEPARENT_META = \"traceparent\";\nconst TRACESTATE_META = \"tracestate\";\nconst INVALID_TRACE_ID = \"00000000000000000000000000000000\";\nconst TRACE_FLAGS_SAMPLED = 0x1;\n// Local instance — extraction must not depend on whether the customer (or\n// our own bootstrap) set a global propagator.\nconst W3C_PROPAGATOR = new W3CTraceContextPropagator();\n\nfunction readMeta(name: string): string | null {\n if (typeof document === \"undefined\") {\n return null;\n }\n const el = document.querySelector(`meta[name=\"${name}\"]`);\n return el?.getAttribute(\"content\") ?? null;\n}\n\n/**\n * Reads the W3C `traceparent` (and optional `tracestate`) meta tag from the\n * document head and extracts an OTel `Context`. SSR renderers (Next, Vite,\n * …) inject the server-side request span's context so the client SDK can\n * stitch its spans onto the same trace.\n *\n * Returns `null` when:\n * - called server-side (no `document`)\n * - no `traceparent` meta tag is present\n * - the extracted context has an invalid trace id\n * - the `sampled` flag is unset — the browser doesn't honor the bit when\n * creating child spans, so an unsampled parent would orphan every\n * browser span under a trace with no recorded server segments. Drop\n * instead so spans fall back to a fresh root.\n */\nexport function readPropagationFromDocument(): Context | null {\n const traceparent = readMeta(TRACEPARENT_META);\n if (!traceparent) {\n return null;\n }\n\n const carrier: Record<string, string> = { traceparent };\n const tracestate = readMeta(TRACESTATE_META);\n if (tracestate) {\n carrier[TRACESTATE_META] = tracestate;\n }\n\n const extracted = W3C_PROPAGATOR.extract(\n ROOT_CONTEXT,\n carrier,\n defaultTextMapGetter\n );\n const spanCtx = trace.getSpanContext(extracted);\n // octet of flag bits per the W3C trace-context spec; bit 0 is the sampled\n // bit. Bitwise AND is the canonical extraction.\n if (\n !spanCtx ||\n spanCtx.traceId === INVALID_TRACE_ID ||\n // biome-ignore lint/suspicious/noBitwiseOperators: traceFlags is a single\n (spanCtx.traceFlags & TRACE_FLAGS_SAMPLED) === 0\n ) {\n return null;\n }\n return extracted;\n}\n"],"mappings":"mIAQA,MACM,gBAAkB,aAKlB,eAAiB,IAAI,0BAE3B,SAAS,SAAS,KAA6B,CAK7C,OAJI,OAAO,SAAa,IACf,KAEE,SAAS,cAAc,cAAc,KAAK,GAC7C,GAAG,aAAa,SAAS,GAAK,IACxC,CAiBA,SAAgB,6BAA8C,CAC5D,IAAM,YAAc,SAAS,aAAgB,EAC7C,GAAI,CAAC,YACH,OAAO,KAGT,IAAM,QAAkC,CAAE,WAAY,EAChD,WAAa,SAAS,eAAe,EACvC,aACF,QAAQ,iBAAmB,YAG7B,IAAM,UAAY,eAAe,QAC/B,aACA,QACA,oBACF,EACM,QAAU,MAAM,eAAe,SAAS,EAW9C,MAPE,CAAC,SACD,QAAQ,UAAY,oCAAA,EAEnB,QAAQ,WAAa,GAEf,KAEF,SACT"}
1
+ {"version":3,"file":"propagation.mjs","names":[],"sources":["../../../src/internal/otel/propagation.ts"],"sourcesContent":["import {\n type Context,\n defaultTextMapGetter,\n ROOT_CONTEXT,\n trace,\n} from \"@opentelemetry/api\";\nimport { W3CTraceContextPropagator } from \"@opentelemetry/core\";\n\nconst TRACEPARENT_META = \"traceparent\";\nconst TRACESTATE_META = \"tracestate\";\nconst INVALID_TRACE_ID = \"00000000000000000000000000000000\";\nconst TRACE_FLAGS_SAMPLED = 0x1;\n\nconst W3C_PROPAGATOR = new W3CTraceContextPropagator();\n\nfunction readMeta(name: string): string | null {\n if (typeof document === \"undefined\") {\n return null;\n }\n const el = document.querySelector(`meta[name=\"${name}\"]`);\n return el?.getAttribute(\"content\") ?? null;\n}\n\n/**\n * Reads the W3C `traceparent` (and optional `tracestate`) meta tag from the\n * document head and extracts an OTel `Context`. SSR renderers (Next, Vite,\n * …) inject the server-side request span's context so the client SDK can\n * stitch its spans onto the same trace.\n *\n * Returns `null` when:\n * - called server-side (no `document`)\n * - no `traceparent` meta tag is present\n * - the extracted context has an invalid trace id\n * - the `sampled` flag is unset — the browser doesn't honor the bit when\n * creating child spans, so an unsampled parent would orphan every\n * browser span under a trace with no recorded server segments. Drop\n * instead so spans fall back to a fresh root.\n */\nexport function readPropagationFromDocument(): Context | null {\n const traceparent = readMeta(TRACEPARENT_META);\n if (!traceparent) {\n return null;\n }\n\n const carrier: Record<string, string> = { traceparent };\n const tracestate = readMeta(TRACESTATE_META);\n if (tracestate) {\n carrier[TRACESTATE_META] = tracestate;\n }\n\n const extracted = W3C_PROPAGATOR.extract(\n ROOT_CONTEXT,\n carrier,\n defaultTextMapGetter\n );\n const spanCtx = trace.getSpanContext(extracted);\n\n if (\n !spanCtx ||\n spanCtx.traceId === INVALID_TRACE_ID ||\n // biome-ignore lint/suspicious/noBitwiseOperators: traceFlags is a single\n (spanCtx.traceFlags & TRACE_FLAGS_SAMPLED) === 0\n ) {\n return null;\n }\n return extracted;\n}\n"],"mappings":"mIAQA,MACM,gBAAkB,aAIlB,eAAiB,IAAI,0BAE3B,SAAS,SAAS,KAA6B,CAK7C,OAJI,OAAO,SAAa,IACf,KAEE,SAAS,cAAc,cAAc,KAAK,GAC7C,GAAG,aAAa,SAAS,GAAK,IACxC,CAiBA,SAAgB,6BAA8C,CAC5D,IAAM,YAAc,SAAS,aAAgB,EAC7C,GAAI,CAAC,YACH,OAAO,KAGT,IAAM,QAAkC,CAAE,WAAY,EAChD,WAAa,SAAS,eAAe,EACvC,aACF,QAAQ,iBAAmB,YAG7B,IAAM,UAAY,eAAe,QAC/B,aACA,QACA,oBACF,EACM,QAAU,MAAM,eAAe,SAAS,EAU9C,MAPE,CAAC,SACD,QAAQ,UAAY,oCAAA,EAEnB,QAAQ,WAAa,GAEf,KAEF,SACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"provider.d.mts","names":[],"sources":["../../../src/internal/otel/provider.ts"],"mappings":";;;;;;;;;UA0GiB,iBAAA;;AAAjB;;;;;;;;;EAWE,6BAAA,GAAgC,kBAAA;EA8CP;;;;;;;;;;EAnCzB,uBAAA,GAA0B,YAAA;EAiC1B;;;;;;EA1BA,wBAAA,GAA2B,aAAA;EAuChB;AAGb;;;;EApCE,WAAA,EAAa,OAAA;EA6CG;EA3ChB,YAAA;EA6Cc;;;;;;;;;EAnCd,QAAA;EAiCgB;;;;;;;EAzBhB,YAAA,QAAoB,SAAA;EA8BJ;EA5BhB,WAAA,GAAc,WAAA;EA4BmB;AAWnC;;;;EAjCE,QAAA;EAkCA;;;AACmB;EA9BnB,WAAA;AAAA;AAAA,UAGe,kBAAA;;;;;;EAMf,cAAA,EAAgB,uBAAA;;EAEhB,KAAA,IAAS,OAAA;EACT,cAAA,EAAgB,cAAA;EAChB,aAAA,EAAe,aAAA;EACf,YAAA,EAAc,YAAA;;EAEd,QAAA,IAAY,OAAA;EACZ,cAAA,EAAgB,iBAAA;AAAA;;;;;;;;;iBAWF,iBAAA,CACd,KAAA,EAAO,iBAAA,GACN,kBAAkB"}
1
+ {"version":3,"file":"provider.d.mts","names":[],"sources":["../../../src/internal/otel/provider.ts"],"mappings":";;;;;;;;;UAoGiB,iBAAA;;AAAjB;;;;;;;;;EAWE,6BAAA,GAAgC,kBAAA;EA8CP;;;;;;;;;;EAnCzB,uBAAA,GAA0B,YAAA;EAiC1B;;;;;;EA1BA,wBAAA,GAA2B,aAAA;EAuChB;AAGb;;;;EApCE,WAAA,EAAa,OAAA;EA6CG;EA3ChB,YAAA;EA6Cc;;;;;;;;;EAnCd,QAAA;EAiCgB;;;;;;;EAzBhB,YAAA,QAAoB,SAAA;EA8BJ;EA5BhB,WAAA,GAAc,WAAA;EA4BmB;AAWnC;;;;EAjCE,QAAA;EAkCA;;;AACmB;EA9BnB,WAAA;AAAA;AAAA,UAGe,kBAAA;;;;;;EAMf,cAAA,EAAgB,uBAAA;;EAEhB,KAAA,IAAS,OAAA;EACT,cAAA,EAAgB,cAAA;EAChB,aAAA,EAAe,aAAA;EACf,YAAA,EAAc,YAAA;;EAEd,QAAA,IAAY,OAAA;EACZ,cAAA,EAAgB,iBAAA;AAAA;;;;;;;;;iBAWF,iBAAA,CACd,KAAA,EAAO,iBAAA,GACN,kBAAkB"}
@@ -1 +1 @@
1
- import{SDK_NAME,SDK_VERSION}from"../version.mjs";import{createBeaconLogExporter,createBeaconMetricExporter,createBeaconTraceExporter}from"./exporter.mjs";import{PageScopeContextManager}from"./page-scope-context-manager.mjs";import{propagation}from"@opentelemetry/api";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{MeterProvider,PeriodicExportingMetricReader}from"@opentelemetry/sdk-metrics";import{normalizeEnv}from"@interfere/types/sdk/runtime";import{browserDetector}from"@opentelemetry/opentelemetry-browser-detector";import{detectResources,resourceFromAttributes}from"@opentelemetry/resources";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{WebTracerProvider}from"@opentelemetry/sdk-trace-web";import{createSessionLogRecordProcessor,createSessionSpanProcessor}from"@opentelemetry/web-common";const BROWSER_BATCH_OPTIONS={maxQueueSize:50,maxExportBatchSize:10,scheduledDelayMillis:2e3,exportTimeoutMillis:1e4};function buildOtelProvider(input){let environment=normalizeEnv(typeof process>`u`?void 0:process.env.NODE_ENV)??`unknown`,baseResource=resourceFromAttributes({"service.name":input.serviceName??`interfere-sdk`,"service.namespace":`interfere`,"deployment.environment.name":environment,"telemetry.sdk.language":`webjs`,"interfere.sdk.name":SDK_NAME,"interfere.sdk.version":SDK_VERSION,"interfere.sdk.stack":input.sdkStack.join(`, `),...input.releaseSlug?{"release.slug":input.releaseSlug}:{},...input.deviceId?{"device.id":input.deviceId}:{}}),detected=detectResources({detectors:[browserDetector]}),resource=baseResource.merge(detected),traceExporter=createBeaconTraceExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),getSessionId=input.getSessionId,tracerProvider=new WebTracerProvider({resource,spanProcessors:[createSessionSpanProcessor({getSessionId}),new BatchSpanProcessor(traceExporter,BROWSER_BATCH_OPTIONS),...input.additionalSpanProcessors??[]]}),contextManager=new PageScopeContextManager;tracerProvider.register({contextManager}),propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,new W3CBaggagePropagator]}));let metricReader=new PeriodicExportingMetricReader({exporter:createBeaconMetricExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),exportIntervalMillis:3e4}),meterProvider=new MeterProvider({resource,readers:[metricReader,...input.additionalMetricReaders??[]]}),logExporter=createBeaconLogExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),loggerProvider=new LoggerProvider({resource,processors:[createSessionLogRecordProcessor({getSessionId}),new BatchLogRecordProcessor(logExporter,BROWSER_BATCH_OPTIONS),...input.additionalLogRecordProcessors??[]]});return{contextManager,tracerProvider,meterProvider,metricReader,loggerProvider,flush:async()=>{await Promise.all([tracerProvider.forceFlush(),metricReader.forceFlush(),loggerProvider.forceFlush()])},shutdown:async()=>{await Promise.all([tracerProvider.shutdown(),meterProvider.shutdown(),loggerProvider.shutdown()])}}}export{buildOtelProvider};
1
+ import{getGlobal}from"../../util/global.mjs";import{SDK_NAME,SDK_VERSION}from"../version.mjs";import{createBeaconLogExporter,createBeaconMetricExporter,createBeaconTraceExporter}from"./exporter.mjs";import{PageScopeContextManager}from"./page-scope-context-manager.mjs";import{propagation}from"@opentelemetry/api";import{CompositePropagator,W3CBaggagePropagator,W3CTraceContextPropagator}from"@opentelemetry/core";import{MeterProvider,PeriodicExportingMetricReader}from"@opentelemetry/sdk-metrics";import{resolveEnvironment}from"@interfere/types/sdk/runtime";import{browserDetector}from"@opentelemetry/opentelemetry-browser-detector";import{detectResources,resourceFromAttributes}from"@opentelemetry/resources";import{BatchLogRecordProcessor,LoggerProvider}from"@opentelemetry/sdk-logs";import{BatchSpanProcessor}from"@opentelemetry/sdk-trace-base";import{WebTracerProvider}from"@opentelemetry/sdk-trace-web";import{createSessionLogRecordProcessor,createSessionSpanProcessor}from"@opentelemetry/web-common";const BROWSER_BATCH_OPTIONS={maxQueueSize:50,maxExportBatchSize:10,scheduledDelayMillis:2e3,exportTimeoutMillis:1e4};function buildOtelProvider(input){let environment=resolveEnvironment({explicit:getGlobal(`__INTERFERE_ENVIRONMENT__`),env:typeof process>`u`?{}:{INTERFERE_ENVIRONMENT:process.env.INTERFERE_ENVIRONMENT,VERCEL_ENV:process.env.VERCEL_ENV,NODE_ENV:process.env.NODE_ENV}}),baseResource=resourceFromAttributes({"service.name":input.serviceName??`interfere-sdk`,"service.namespace":`interfere`,"deployment.environment.name":environment,"telemetry.sdk.language":`webjs`,"interfere.sdk.name":SDK_NAME,"interfere.sdk.version":SDK_VERSION,"interfere.sdk.stack":input.sdkStack.join(`, `),...input.releaseSlug?{"release.slug":input.releaseSlug}:{},...input.deviceId?{"device.id":input.deviceId}:{}}),detected=detectResources({detectors:[browserDetector]}),resource=baseResource.merge(detected),traceExporter=createBeaconTraceExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),getSessionId=input.getSessionId,tracerProvider=new WebTracerProvider({resource,spanProcessors:[createSessionSpanProcessor({getSessionId}),new BatchSpanProcessor(traceExporter,BROWSER_BATCH_OPTIONS),...input.additionalSpanProcessors??[]]}),contextManager=new PageScopeContextManager;tracerProvider.register({contextManager}),propagation.fields().length===0&&propagation.setGlobalPropagator(new CompositePropagator({propagators:[new W3CTraceContextPropagator,new W3CBaggagePropagator]}));let metricReader=new PeriodicExportingMetricReader({exporter:createBeaconMetricExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),exportIntervalMillis:3e4}),meterProvider=new MeterProvider({resource,readers:[metricReader,...input.additionalMetricReaders??[]]}),logExporter=createBeaconLogExporter({collectorUrl:input.collectorUrl,authHeaders:input.authHeaders}),loggerProvider=new LoggerProvider({resource,processors:[createSessionLogRecordProcessor({getSessionId}),new BatchLogRecordProcessor(logExporter,BROWSER_BATCH_OPTIONS),...input.additionalLogRecordProcessors??[]]});return{contextManager,tracerProvider,meterProvider,metricReader,loggerProvider,flush:async()=>{await Promise.all([tracerProvider.forceFlush(),metricReader.forceFlush(),loggerProvider.forceFlush()])},shutdown:async()=>{await Promise.all([tracerProvider.shutdown(),meterProvider.shutdown(),loggerProvider.shutdown()])}}}export{buildOtelProvider};
@@ -1 +1 @@
1
- {"version":3,"file":"provider.mjs","names":[],"sources":["../../../src/internal/otel/provider.ts"],"sourcesContent":["import type { SessionId } from \"@interfere/types/data/session\";\nimport type { ReleaseSlug } from \"@interfere/types/releases/slug\";\nimport { normalizeEnv } from \"@interfere/types/sdk/runtime\";\n\nimport { propagation } from \"@opentelemetry/api\";\nimport {\n CompositePropagator,\n W3CBaggagePropagator,\n W3CTraceContextPropagator,\n} from \"@opentelemetry/core\";\nimport { browserDetector } from \"@opentelemetry/opentelemetry-browser-detector\";\nimport {\n detectResources,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport {\n BatchLogRecordProcessor,\n LoggerProvider,\n type LogRecordProcessor,\n} from \"@opentelemetry/sdk-logs\";\nimport {\n MeterProvider,\n type MetricReader,\n PeriodicExportingMetricReader,\n} from \"@opentelemetry/sdk-metrics\";\nimport {\n BatchSpanProcessor,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { WebTracerProvider } from \"@opentelemetry/sdk-trace-web\";\nimport {\n createSessionLogRecordProcessor,\n createSessionSpanProcessor,\n} from \"@opentelemetry/web-common\";\n\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\nimport {\n createBeaconLogExporter,\n createBeaconMetricExporter,\n createBeaconTraceExporter,\n} from \"./exporter.js\";\nimport { PageScopeContextManager } from \"./page-scope-context-manager.js\";\n\nconst SERVICE_NAMESPACE = \"interfere\";\n\n// Inlined wire keys — matches the pattern in\n// `@interfere/observability/browser/rum` (semconv keeps shifting these\n// between minor versions, but the wire keys themselves are stable).\nconst ATTR_SERVICE_NAME = \"service.name\" as const;\nconst ATTR_SERVICE_NAMESPACE = \"service.namespace\" as const;\nconst ATTR_DEPLOYMENT_ENVIRONMENT_NAME = \"deployment.environment.name\" as const;\nconst ATTR_TELEMETRY_SDK_LANGUAGE = \"telemetry.sdk.language\" as const;\nconst ATTR_RELEASE_SLUG = \"release.slug\" as const;\nconst ATTR_INTERFERE_SDK_STACK = \"interfere.sdk.stack\" as const;\nconst ATTR_INTERFERE_SDK_NAME = \"interfere.sdk.name\" as const;\nconst ATTR_INTERFERE_SDK_VERSION = \"interfere.sdk.version\" as const;\n// OTel semconv-canonical name for a stable per-device identifier.\n// Stamped as a *resource* attribute (not per-span) because it's stable\n// for the lifetime of the page; sessions rotate, devices don't. The\n// collector reads this off `context.resourceAttributes[\"device.id\"]`\n// and projects it into `spans.deviceId`.\nconst ATTR_DEVICE_ID = \"device.id\" as const;\n\nconst DEFAULT_SERVICE_NAME = \"interfere-sdk\";\n\n/**\n * Tuned for `navigator.sendBeacon`'s per-call payload ceiling.\n *\n * The browser primary exporters dispatch every export via beacon\n * (see `BeaconTraceExporter` / `BeaconLogExporter` /\n * `BeaconMetricExporter` in `exporter.ts`) — sendBeacon is the only\n * browser transport that reliably commits a request the page is also\n * tearing down. It's not free though: most browsers cap each beacon\n * payload at 64KiB (Chrome / Firefox) and reject anything larger\n * with a `false` return from `navigator.sendBeacon` — at which point\n * the BSP just drops the batch (no retry: the SW backstop intercepts\n * 5xx / network failures, not user-agent rejections).\n *\n * The numbers below keep payloads comfortably under that ceiling,\n * even on jank-heavy pages (production data on 2026-05-11 saw 831\n * `longtask` spans piled up on a single page-hide, observed via the\n * BetterStack dual-write) where a 100-span batch would already top\n * 100KiB:\n *\n * - `maxExportBatchSize: 10` — caps each beacon at ~10–20KiB,\n * leaving headroom for span attribute bloat without flirting\n * with the per-call ceiling.\n * - `maxQueueSize: 50` — caps the worst-case unload payload at\n * ~50–100KiB; the BSP drains across 5 beacons when forced-\n * flushed on `visibilitychange→hidden`, all of which queue\n * synchronously inside the user agent.\n * - `scheduledDelayMillis: 2000` — quieter pages keep less in\n * buffer at any moment, so a hidden→visible→hidden bounce\n * doesn't accumulate 5s of telemetry waiting to ship.\n * - `exportTimeoutMillis: 10_000` — unchanged. Beacon dispatches\n * are essentially synchronous (the user agent enqueues into its\n * own send loop), so the timeout is mostly defensive against\n * pathological `JsonTraceSerializer.serializeRequest` calls.\n */\nconst BROWSER_BATCH_OPTIONS = {\n maxQueueSize: 50,\n maxExportBatchSize: 10,\n scheduledDelayMillis: 2000,\n exportTimeoutMillis: 10_000,\n} as const;\n\nexport interface OtelProviderInput {\n /**\n * @internal\n *\n * Extra log-record processors fanned into the LoggerProvider's\n * processor list at construction. Used by `@interfere/observability`\n * for internal-only dual-write to BetterStack — keeps a parallel\n * destination's queue/exporter independent of the SDK's own.\n * Customers don't get a fan-out hook on the SDK surface; this is a\n * private bridge for our own dogfood apps.\n */\n additionalLogRecordProcessors?: LogRecordProcessor[];\n /**\n * @internal\n *\n * Extra metric readers fanned into the MeterProvider's reader list\n * at construction. Used by `@interfere/observability` for internal-\n * only dual-write of web vitals histograms (`web_vitals.{ttfb,fcp,\n * lcp,inp,cls}`) to the BetterStack-fronting OTel collector. Each\n * reader owns its own queue + exporter so a 5xx / network outage on\n * the parallel destination backs off only its own batch.\n */\n additionalMetricReaders?: MetricReader[];\n /**\n * @internal\n *\n * Extra span processors fanned into the WebTracerProvider's processor\n * list at construction. See `additionalLogRecordProcessors`.\n */\n additionalSpanProcessors?: SpanProcessor[];\n /**\n * Auth + content headers for OTLP exports. Sourced from the kernel's\n * already-resolved ingest target so the OTLP path inherits the same\n * transport metadata the rest of the SDK uses — no separate auth knob.\n */\n authHeaders: Headers;\n /** OTLP base URL — the opaque sink path is appended by the exporters. */\n collectorUrl: string;\n /**\n * Stable per-device identifier from `DeviceManager`. Resolved from\n * localStorage / cookie at kernel boot, so it's synchronously\n * available before the providers are constructed. Stamped as the\n * OTel-semconv `device.id` resource attribute so every span / log /\n * metric carries it without per-record re-stamping. Optional —\n * tests and SSR boot paths that build the provider before the\n * device manager has run pass `null`.\n */\n deviceId?: string | null | undefined;\n /**\n * Returns the kernel's current session id. Wired into per-span /\n * per-log session processors so a mid-page session rotation (30-min\n * idle expiry, manual reset) lands on subsequent spans without the\n * kernel having to rebuild the providers. Returns `null` before the\n * `SessionTracker` has bootstrapped.\n */\n getSessionId: () => SessionId | null;\n /** Optional release slug — top-level `release.slug` attr (Phase 6 wire identity). */\n releaseSlug?: ReleaseSlug | null | undefined;\n /**\n * Producer chain for the `interfere.sdk.stack` resource attribute —\n * e.g. `[\"@interfere/next@10.0.0\", \"@interfere/react@10.0.0\"]`.\n * Wrapper-injected; the kernel just forwards.\n */\n sdkStack: string[];\n /**\n * Override the OTel `service.name` resource attribute. Defaults to\n * `\"interfere-sdk\"`.\n */\n serviceName?: string;\n}\n\nexport interface OtelProviderHandle {\n /**\n * Page-scope context manager. The kernel calls `setPageScope(ctx)` after\n * extracting the SSR `traceparent` meta tag so spans without a more\n * specific zone descend from the server-side parent.\n */\n contextManager: PageScopeContextManager;\n /** Force-flush all three providers — call from kernel.flush() / on visibility hidden. */\n flush(): Promise<void>;\n loggerProvider: LoggerProvider;\n meterProvider: MeterProvider;\n metricReader: MetricReader;\n /** Tear down all three providers — called from kernel.dispose(). */\n shutdown(): Promise<void>;\n tracerProvider: WebTracerProvider;\n}\n\n/**\n * Constructs the SDK's **private** OTel providers — never registers globally\n * via `provider.register()` or `trace.setGlobalTracerProvider()`. Customers\n * with their own OTel setup (DataDog, Vercel, etc.) cohabit cleanly.\n *\n * Splitting this out from `Kernel` keeps the OTel module lazy-loadable so\n * the error-only bundle path (no tracing) never imports the OTel SDK.\n */\nexport function buildOtelProvider(\n input: OtelProviderInput\n): OtelProviderHandle {\n // Static service identity merged with detected browser attrs\n // (`browser.brands`, `browser.platform`, `browser.mobile`,\n // `browser.language`, `browser.user_agent`). The browser detector\n // reads UA Client Hints synchronously via `navigator.userAgentData`\n // low-entropy properties, so this stays sync — async would race\n // `DocumentLoadInstrumentation` and consistently miss page-load.\n const environment =\n normalizeEnv(\n typeof process === \"undefined\" ? undefined : process.env[\"NODE_ENV\"]\n ) ?? \"unknown\";\n\n const baseResource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: input.serviceName ?? DEFAULT_SERVICE_NAME,\n [ATTR_SERVICE_NAMESPACE]: SERVICE_NAMESPACE,\n [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: environment,\n [ATTR_TELEMETRY_SDK_LANGUAGE]: \"webjs\",\n [ATTR_INTERFERE_SDK_NAME]: SDK_NAME,\n [ATTR_INTERFERE_SDK_VERSION]: SDK_VERSION,\n [ATTR_INTERFERE_SDK_STACK]: input.sdkStack.join(\", \"),\n ...(input.releaseSlug ? { [ATTR_RELEASE_SLUG]: input.releaseSlug } : {}),\n ...(input.deviceId ? { [ATTR_DEVICE_ID]: input.deviceId } : {}),\n });\n const detected = detectResources({ detectors: [browserDetector] });\n const resource = baseResource.merge(detected);\n\n const traceExporter = createBeaconTraceExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n\n // The web-common session processor expects `() => string | null`,\n // matching our `KernelSession.getId` shape.\n const getSessionId = input.getSessionId;\n\n const spanProcessors: SpanProcessor[] = [\n // Stamps `session.id` per-span, re-reading on every export. A\n // mid-page session rotation (30-min idle, manual reset) lands on\n // subsequent spans without rebuilding the provider. Resource-level\n // `session.id` would freeze at boot.\n createSessionSpanProcessor({ getSessionId }),\n // Exception attribute defaults (`interfere.exception.{mechanism,\n // handled,kind}`) are no longer stamped here — third-party\n // auto-instrumentations bypass our kernel anyway, so the SDK\n // can't reliably tell what to default. Server-side enrichment\n // (`enrichment/lib/normalize-atom.ts`) is the single chokepoint\n // every exception event flows through, so the defaults live\n // there now and the SDK ships less code.\n new BatchSpanProcessor(traceExporter, BROWSER_BATCH_OPTIONS),\n // Internal-only fan-out (BetterStack dual-write for `interfere/homepage`\n // + `interfere/dashboard`). Each extra processor owns its own queue +\n // exporter, so a 5xx / network outage on the parallel destination\n // backs off only its own batch — the primary collector pipeline keeps\n // draining unaffected.\n ...(input.additionalSpanProcessors ?? []),\n ];\n\n const tracerProvider = new WebTracerProvider({ resource, spanProcessors });\n\n // `PageScopeContextManager` keeps the active context alive across\n // microtasks/promise chains/setTimeout, AND falls back to the page-scope\n // context (the SSR `traceparent`) for code paths that fire outside any\n // zone the manager itself created. The kernel populates the page scope\n // by calling `contextManager.setPageScope(ctx)` after extracting the\n // meta tag.\n //\n // `register({ contextManager })` does call `setGlobalContextManager`\n // internally — that's unavoidable; OTel exposes exactly one global\n // context manager. Customers with their own OTel provider boot last and\n // win. We accept the trade-off for correctness: the alternative\n // (separate per-provider context manager) is not supported by OTel.\n const contextManager = new PageScopeContextManager();\n tracerProvider.register({ contextManager });\n\n // Composed W3C trace context + baggage propagator. `FetchInstrumentation`\n // injects both `traceparent` and `baggage` on outgoing requests so\n // backend Elysia plugins can pick `interfere.*` baggage entries off\n // the request and stamp them on server spans. Only set the global if\n // no propagator is currently registered (`fields()` is `[]` for the\n // noop). Customers who installed their own propagator (B3, composite,\n // …) are preserved. Reading the SSR meta tag uses a local instance,\n // not the global, so our extract path is independent.\n if (propagation.fields().length === 0) {\n propagation.setGlobalPropagator(\n new CompositePropagator({\n propagators: [\n new W3CTraceContextPropagator(),\n new W3CBaggagePropagator(),\n ],\n })\n );\n }\n\n const metricExporter = createBeaconMetricExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n\n const metricReader = new PeriodicExportingMetricReader({\n exporter: metricExporter,\n exportIntervalMillis: 30_000,\n });\n\n const meterProvider = new MeterProvider({\n resource,\n readers: [metricReader, ...(input.additionalMetricReaders ?? [])],\n });\n\n // Logs path. The `plugins/logs.ts` plugin patches `console.*` and emits\n // a `LogRecord` per call (errorsPlugin still owns Error-bearing console\n // calls — the class boundary is checked there). LoggerProvider is\n // private; the kernel exposes `recordLog` so callers don't have to\n // touch the OTel logs API directly.\n const logExporter = createBeaconLogExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n const loggerProvider = new LoggerProvider({\n resource,\n processors: [\n // Mirrors the SessionSpanProcessor for traces — every log record\n // (including `BrowserNavigationInstrumentation`'s navigation\n // events) carries the same `session.id` as the spans emitted\n // around it, so trace ↔ log correlation by session is trivial.\n createSessionLogRecordProcessor({ getSessionId }),\n new BatchLogRecordProcessor(logExporter, BROWSER_BATCH_OPTIONS),\n ...(input.additionalLogRecordProcessors ?? []),\n ],\n });\n\n return {\n contextManager,\n tracerProvider,\n meterProvider,\n metricReader,\n loggerProvider,\n flush: async () => {\n await Promise.all([\n tracerProvider.forceFlush(),\n metricReader.forceFlush(),\n loggerProvider.forceFlush(),\n ]);\n },\n shutdown: async () => {\n await Promise.all([\n tracerProvider.shutdown(),\n meterProvider.shutdown(),\n loggerProvider.shutdown(),\n ]);\n },\n };\n}\n"],"mappings":"27BA2CA,MAwDM,sBAAwB,CAC5B,aAAc,GACd,mBAAoB,GACpB,qBAAsB,IACtB,oBAAqB,GACvB,EAkGA,SAAgB,kBACd,MACoB,CAOpB,IAAM,YACJ,aACE,OAAO,QAAY,IAAc,IAAA,GAAY,QAAQ,IAAI,QAC3D,GAAK,UAED,aAAe,uBAAuB,CACzC,eAAoB,MAAM,aAAe,gBACzC,oBAAyB,YACzB,8BAAmC,YACnC,yBAA8B,QAC9B,qBAA0B,SAC1B,wBAA6B,YAC7B,sBAA2B,MAAM,SAAS,KAAK,IAAI,EACpD,GAAI,MAAM,YAAc,CAAG,eAAoB,MAAM,WAAY,EAAI,CAAC,EACtE,GAAI,MAAM,SAAW,CAAG,YAAiB,MAAM,QAAS,EAAI,CAAC,CAC/D,CAAC,EACK,SAAW,gBAAgB,CAAE,UAAW,CAAC,eAAe,CAAE,CAAC,EAC3D,SAAW,aAAa,MAAM,QAAQ,EAEtC,cAAgB,0BAA0B,CAC9C,aAAc,MAAM,aACpB,YAAa,MAAM,WACrB,CAAC,EAIK,aAAe,MAAM,aAwBrB,eAAiB,IAAI,kBAAkB,CAAE,SAAU,eAAA,CAjBvD,2BAA2B,CAAE,YAAa,CAAC,EAQ3C,IAAI,mBAAmB,cAAe,qBAAqB,EAM3D,GAAI,MAAM,0BAA4B,CAAC,CAG6B,CAAE,CAAC,EAcnE,eAAiB,IAAI,wBAC3B,eAAe,SAAS,CAAE,cAAe,CAAC,EAUtC,YAAY,OAAO,EAAE,SAAW,GAClC,YAAY,oBACV,IAAI,oBAAoB,CACtB,YAAa,CACX,IAAI,0BACJ,IAAI,oBACN,CACF,CAAC,CACH,EAQF,IAAM,aAAe,IAAI,8BAA8B,CACrD,SANqB,2BAA2B,CAChD,aAAc,MAAM,aACpB,YAAa,MAAM,WACrB,CAGyB,EACvB,qBAAsB,GACxB,CAAC,EAEK,cAAgB,IAAI,cAAc,CACtC,SACA,QAAS,CAAC,aAAc,GAAI,MAAM,yBAA2B,CAAC,CAAE,CAClE,CAAC,EAOK,YAAc,wBAAwB,CAC1C,aAAc,MAAM,aACpB,YAAa,MAAM,WACrB,CAAC,EACK,eAAiB,IAAI,eAAe,CACxC,SACA,WAAY,CAKV,gCAAgC,CAAE,YAAa,CAAC,EAChD,IAAI,wBAAwB,YAAa,qBAAqB,EAC9D,GAAI,MAAM,+BAAiC,CAAC,CAC9C,CACF,CAAC,EAED,MAAO,CACL,eACA,eACA,cACA,aACA,eACA,MAAO,SAAY,CACjB,MAAM,QAAQ,IAAI,CAChB,eAAe,WAAW,EAC1B,aAAa,WAAW,EACxB,eAAe,WAAW,CAC5B,CAAC,CACH,EACA,SAAU,SAAY,CACpB,MAAM,QAAQ,IAAI,CAChB,eAAe,SAAS,EACxB,cAAc,SAAS,EACvB,eAAe,SAAS,CAC1B,CAAC,CACH,CACF,CACF"}
1
+ {"version":3,"file":"provider.mjs","names":[],"sources":["../../../src/internal/otel/provider.ts"],"sourcesContent":["import type { SessionId } from \"@interfere/types/data/session\";\nimport type { ReleaseSlug } from \"@interfere/types/releases/slug\";\nimport { resolveEnvironment } from \"@interfere/types/sdk/runtime\";\n\nimport { propagation } from \"@opentelemetry/api\";\nimport {\n CompositePropagator,\n W3CBaggagePropagator,\n W3CTraceContextPropagator,\n} from \"@opentelemetry/core\";\nimport { browserDetector } from \"@opentelemetry/opentelemetry-browser-detector\";\nimport {\n detectResources,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport {\n BatchLogRecordProcessor,\n LoggerProvider,\n type LogRecordProcessor,\n} from \"@opentelemetry/sdk-logs\";\nimport {\n MeterProvider,\n type MetricReader,\n PeriodicExportingMetricReader,\n} from \"@opentelemetry/sdk-metrics\";\nimport {\n BatchSpanProcessor,\n type SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\nimport { WebTracerProvider } from \"@opentelemetry/sdk-trace-web\";\nimport {\n createSessionLogRecordProcessor,\n createSessionSpanProcessor,\n} from \"@opentelemetry/web-common\";\n\nimport { getGlobal } from \"../../util/global.js\";\nimport { SDK_NAME, SDK_VERSION } from \"../version.js\";\nimport {\n createBeaconLogExporter,\n createBeaconMetricExporter,\n createBeaconTraceExporter,\n} from \"./exporter.js\";\nimport { PageScopeContextManager } from \"./page-scope-context-manager.js\";\n\nconst SERVICE_NAMESPACE = \"interfere\";\n\nconst ATTR_SERVICE_NAME = \"service.name\" as const;\nconst ATTR_SERVICE_NAMESPACE = \"service.namespace\" as const;\nconst ATTR_DEPLOYMENT_ENVIRONMENT_NAME = \"deployment.environment.name\" as const;\nconst ATTR_TELEMETRY_SDK_LANGUAGE = \"telemetry.sdk.language\" as const;\nconst ATTR_RELEASE_SLUG = \"release.slug\" as const;\nconst ATTR_INTERFERE_SDK_STACK = \"interfere.sdk.stack\" as const;\nconst ATTR_INTERFERE_SDK_NAME = \"interfere.sdk.name\" as const;\nconst ATTR_INTERFERE_SDK_VERSION = \"interfere.sdk.version\" as const;\n\nconst ATTR_DEVICE_ID = \"device.id\" as const;\n\nconst DEFAULT_SERVICE_NAME = \"interfere-sdk\";\n\n/**\n * Tuned for `navigator.sendBeacon`'s per-call payload ceiling.\n *\n * The browser primary exporters dispatch every export via beacon\n * (see `BeaconTraceExporter` / `BeaconLogExporter` /\n * `BeaconMetricExporter` in `exporter.ts`) — sendBeacon is the only\n * browser transport that reliably commits a request the page is also\n * tearing down. It's not free though: most browsers cap each beacon\n * payload at 64KiB (Chrome / Firefox) and reject anything larger\n * with a `false` return from `navigator.sendBeacon` — at which point\n * the BSP just drops the batch (no retry: the SW backstop intercepts\n * 5xx / network failures, not user-agent rejections).\n *\n * The numbers below keep payloads comfortably under that ceiling,\n * even on jank-heavy pages (production data on 2026-05-11 saw 831\n * `longtask` spans piled up on a single page-hide, observed via the\n * BetterStack dual-write) where a 100-span batch would already top\n * 100KiB:\n *\n * - `maxExportBatchSize: 10` — caps each beacon at ~10–20KiB,\n * leaving headroom for span attribute bloat without flirting\n * with the per-call ceiling.\n * - `maxQueueSize: 50` — caps the worst-case unload payload at\n * ~50–100KiB; the BSP drains across 5 beacons when forced-\n * flushed on `visibilitychange→hidden`, all of which queue\n * synchronously inside the user agent.\n * - `scheduledDelayMillis: 2000` — quieter pages keep less in\n * buffer at any moment, so a hidden→visible→hidden bounce\n * doesn't accumulate 5s of telemetry waiting to ship.\n * - `exportTimeoutMillis: 10_000` — unchanged. Beacon dispatches\n * are essentially synchronous (the user agent enqueues into its\n * own send loop), so the timeout is mostly defensive against\n * pathological `JsonTraceSerializer.serializeRequest` calls.\n */\nconst BROWSER_BATCH_OPTIONS = {\n maxQueueSize: 50,\n maxExportBatchSize: 10,\n scheduledDelayMillis: 2000,\n exportTimeoutMillis: 10_000,\n} as const;\n\nexport interface OtelProviderInput {\n /**\n * @internal\n *\n * Extra log-record processors fanned into the LoggerProvider's\n * processor list at construction. Used by `@interfere/observability`\n * for internal-only dual-write to BetterStack — keeps a parallel\n * destination's queue/exporter independent of the SDK's own.\n * Customers don't get a fan-out hook on the SDK surface; this is a\n * private bridge for our own dogfood apps.\n */\n additionalLogRecordProcessors?: LogRecordProcessor[];\n /**\n * @internal\n *\n * Extra metric readers fanned into the MeterProvider's reader list\n * at construction. Used by `@interfere/observability` for internal-\n * only dual-write of web vitals histograms (`web_vitals.{ttfb,fcp,\n * lcp,inp,cls}`) to the BetterStack-fronting OTel collector. Each\n * reader owns its own queue + exporter so a 5xx / network outage on\n * the parallel destination backs off only its own batch.\n */\n additionalMetricReaders?: MetricReader[];\n /**\n * @internal\n *\n * Extra span processors fanned into the WebTracerProvider's processor\n * list at construction. See `additionalLogRecordProcessors`.\n */\n additionalSpanProcessors?: SpanProcessor[];\n /**\n * Auth + content headers for OTLP exports. Sourced from the kernel's\n * already-resolved ingest target so the OTLP path inherits the same\n * transport metadata the rest of the SDK uses — no separate auth knob.\n */\n authHeaders: Headers;\n /** OTLP base URL — the opaque sink path is appended by the exporters. */\n collectorUrl: string;\n /**\n * Stable per-device identifier from `DeviceManager`. Resolved from\n * localStorage / cookie at kernel boot, so it's synchronously\n * available before the providers are constructed. Stamped as the\n * OTel-semconv `device.id` resource attribute so every span / log /\n * metric carries it without per-record re-stamping. Optional —\n * tests and SSR boot paths that build the provider before the\n * device manager has run pass `null`.\n */\n deviceId?: string | null | undefined;\n /**\n * Returns the kernel's current session id. Wired into per-span /\n * per-log session processors so a mid-page session rotation (30-min\n * idle expiry, manual reset) lands on subsequent spans without the\n * kernel having to rebuild the providers. Returns `null` before the\n * `SessionTracker` has bootstrapped.\n */\n getSessionId: () => SessionId | null;\n /** Optional release slug — top-level `release.slug` attr (Phase 6 wire identity). */\n releaseSlug?: ReleaseSlug | null | undefined;\n /**\n * Producer chain for the `interfere.sdk.stack` resource attribute —\n * e.g. `[\"@interfere/next@10.0.0\", \"@interfere/react@10.0.0\"]`.\n * Wrapper-injected; the kernel just forwards.\n */\n sdkStack: string[];\n /**\n * Override the OTel `service.name` resource attribute. Defaults to\n * `\"interfere-sdk\"`.\n */\n serviceName?: string;\n}\n\nexport interface OtelProviderHandle {\n /**\n * Page-scope context manager. The kernel calls `setPageScope(ctx)` after\n * extracting the SSR `traceparent` meta tag so spans without a more\n * specific zone descend from the server-side parent.\n */\n contextManager: PageScopeContextManager;\n /** Force-flush all three providers — call from kernel.flush() / on visibility hidden. */\n flush(): Promise<void>;\n loggerProvider: LoggerProvider;\n meterProvider: MeterProvider;\n metricReader: MetricReader;\n /** Tear down all three providers — called from kernel.dispose(). */\n shutdown(): Promise<void>;\n tracerProvider: WebTracerProvider;\n}\n\n/**\n * Constructs the SDK's **private** OTel providers — never registers globally\n * via `provider.register()` or `trace.setGlobalTracerProvider()`. Customers\n * with their own OTel setup (DataDog, Vercel, etc.) cohabit cleanly.\n *\n * Splitting this out from `Kernel` keeps the OTel module lazy-loadable so\n * the error-only bundle path (no tracing) never imports the OTel SDK.\n */\nexport function buildOtelProvider(\n input: OtelProviderInput\n): OtelProviderHandle {\n // Static `process.env.X` member expressions only — bundlers (Turbopack,\n // webpack DefinePlugin, Vite) inline env vars by replacing the exact member\n // expression. Passing the whole `process.env` object reads the browser\n // process shim's empty `env` at runtime instead, so every value comes back\n // undefined and the environment silently falls back to `development`.\n const environment = resolveEnvironment({\n // `withInterfere()` stamps the build-time deploy environment\n // (INTERFERE_ENVIRONMENT ?? VERCEL_ENV) onto globalThis — neither var is\n // ever inlined into browser bundles, so this is the only way they can\n // outrank the env chain below.\n explicit: getGlobal<string>(\"__INTERFERE_ENVIRONMENT__\"),\n env:\n typeof process === \"undefined\"\n ? {}\n : {\n INTERFERE_ENVIRONMENT: process.env[\"INTERFERE_ENVIRONMENT\"],\n VERCEL_ENV: process.env[\"VERCEL_ENV\"],\n NODE_ENV: process.env[\"NODE_ENV\"],\n },\n });\n\n const baseResource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: input.serviceName ?? DEFAULT_SERVICE_NAME,\n [ATTR_SERVICE_NAMESPACE]: SERVICE_NAMESPACE,\n [ATTR_DEPLOYMENT_ENVIRONMENT_NAME]: environment,\n [ATTR_TELEMETRY_SDK_LANGUAGE]: \"webjs\",\n [ATTR_INTERFERE_SDK_NAME]: SDK_NAME,\n [ATTR_INTERFERE_SDK_VERSION]: SDK_VERSION,\n [ATTR_INTERFERE_SDK_STACK]: input.sdkStack.join(\", \"),\n ...(input.releaseSlug ? { [ATTR_RELEASE_SLUG]: input.releaseSlug } : {}),\n ...(input.deviceId ? { [ATTR_DEVICE_ID]: input.deviceId } : {}),\n });\n const detected = detectResources({ detectors: [browserDetector] });\n const resource = baseResource.merge(detected);\n\n const traceExporter = createBeaconTraceExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n\n const getSessionId = input.getSessionId;\n\n const spanProcessors: SpanProcessor[] = [\n createSessionSpanProcessor({ getSessionId }),\n\n new BatchSpanProcessor(traceExporter, BROWSER_BATCH_OPTIONS),\n\n ...(input.additionalSpanProcessors ?? []),\n ];\n\n const tracerProvider = new WebTracerProvider({ resource, spanProcessors });\n\n const contextManager = new PageScopeContextManager();\n tracerProvider.register({ contextManager });\n\n if (propagation.fields().length === 0) {\n propagation.setGlobalPropagator(\n new CompositePropagator({\n propagators: [\n new W3CTraceContextPropagator(),\n new W3CBaggagePropagator(),\n ],\n })\n );\n }\n\n const metricExporter = createBeaconMetricExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n\n const metricReader = new PeriodicExportingMetricReader({\n exporter: metricExporter,\n exportIntervalMillis: 30_000,\n });\n\n const meterProvider = new MeterProvider({\n resource,\n readers: [metricReader, ...(input.additionalMetricReaders ?? [])],\n });\n\n const logExporter = createBeaconLogExporter({\n collectorUrl: input.collectorUrl,\n authHeaders: input.authHeaders,\n });\n const loggerProvider = new LoggerProvider({\n resource,\n processors: [\n createSessionLogRecordProcessor({ getSessionId }),\n new BatchLogRecordProcessor(logExporter, BROWSER_BATCH_OPTIONS),\n ...(input.additionalLogRecordProcessors ?? []),\n ],\n });\n\n return {\n contextManager,\n tracerProvider,\n meterProvider,\n metricReader,\n loggerProvider,\n flush: async () => {\n await Promise.all([\n tracerProvider.forceFlush(),\n metricReader.forceFlush(),\n loggerProvider.forceFlush(),\n ]);\n },\n shutdown: async () => {\n await Promise.all([\n tracerProvider.shutdown(),\n meterProvider.shutdown(),\n loggerProvider.shutdown(),\n ]);\n },\n };\n}\n"],"mappings":"8+BA4CA,MAiDM,sBAAwB,CAC5B,aAAc,GACd,mBAAoB,GACpB,qBAAsB,IACtB,oBAAqB,GACvB,EAkGA,SAAgB,kBACd,MACoB,CAMpB,IAAM,YAAc,mBAAmB,CAKrC,SAAU,UAAkB,2BAA2B,EACvD,IACE,OAAO,QAAY,IACf,CAAC,EACD,CACE,sBAAuB,QAAQ,IAAI,sBACnC,WAAY,QAAQ,IAAI,WACxB,SAAU,QAAQ,IAAI,QACxB,CACR,CAAC,EAEK,aAAe,uBAAuB,CACzC,eAAoB,MAAM,aAAe,gBACzC,oBAAyB,YACzB,8BAAmC,YACnC,yBAA8B,QAC9B,qBAA0B,SAC1B,wBAA6B,YAC7B,sBAA2B,MAAM,SAAS,KAAK,IAAI,EACpD,GAAI,MAAM,YAAc,CAAG,eAAoB,MAAM,WAAY,EAAI,CAAC,EACtE,GAAI,MAAM,SAAW,CAAG,YAAiB,MAAM,QAAS,EAAI,CAAC,CAC/D,CAAC,EACK,SAAW,gBAAgB,CAAE,UAAW,CAAC,eAAe,CAAE,CAAC,EAC3D,SAAW,aAAa,MAAM,QAAQ,EAEtC,cAAgB,0BAA0B,CAC9C,aAAc,MAAM,aACpB,YAAa,MAAM,WACrB,CAAC,EAEK,aAAe,MAAM,aAUrB,eAAiB,IAAI,kBAAkB,CAAE,SAAU,eAAA,CAPvD,2BAA2B,CAAE,YAAa,CAAC,EAE3C,IAAI,mBAAmB,cAAe,qBAAqB,EAE3D,GAAI,MAAM,0BAA4B,CAAC,CAG6B,CAAE,CAAC,EAEnE,eAAiB,IAAI,wBAC3B,eAAe,SAAS,CAAE,cAAe,CAAC,EAEtC,YAAY,OAAO,EAAE,SAAW,GAClC,YAAY,oBACV,IAAI,oBAAoB,CACtB,YAAa,CACX,IAAI,0BACJ,IAAI,oBACN,CACF,CAAC,CACH,EAQF,IAAM,aAAe,IAAI,8BAA8B,CACrD,SANqB,2BAA2B,CAChD,aAAc,MAAM,aACpB,YAAa,MAAM,WACrB,CAGyB,EACvB,qBAAsB,GACxB,CAAC,EAEK,cAAgB,IAAI,cAAc,CACtC,SACA,QAAS,CAAC,aAAc,GAAI,MAAM,yBAA2B,CAAC,CAAE,CAClE,CAAC,EAEK,YAAc,wBAAwB,CAC1C,aAAc,MAAM,aACpB,YAAa,MAAM,WACrB,CAAC,EACK,eAAiB,IAAI,eAAe,CACxC,SACA,WAAY,CACV,gCAAgC,CAAE,YAAa,CAAC,EAChD,IAAI,wBAAwB,YAAa,qBAAqB,EAC9D,GAAI,MAAM,+BAAiC,CAAC,CAC9C,CACF,CAAC,EAED,MAAO,CACL,eACA,eACA,cACA,aACA,eACA,MAAO,SAAY,CACjB,MAAM,QAAQ,IAAI,CAChB,eAAe,WAAW,EAC1B,aAAa,WAAW,EACxB,eAAe,WAAW,CAC5B,CAAC,CACH,EACA,SAAU,SAAY,CACpB,MAAM,QAAQ,IAAI,CAChB,eAAe,SAAS,EACxB,cAAc,SAAS,EACvB,eAAe,SAAS,CAC1B,CAAC,CACH,CACF,CACF"}
@@ -1,21 +1,8 @@
1
1
  //#region src/internal/page-lifecycle.d.ts
2
- /**
3
- * Single subscription point for `visibilitychange` (hidden) and
4
- * `beforeunload`. Modules call `onPageHidden(handler)` and the handler
5
- * fires once per transition into hidden / unload.
6
- *
7
- * Replaces the previous shape where the kernel, the queue, the pages
8
- * plugin, and the replay plugin each attached their own listeners — four
9
- * subscribers racing to flush / drain / emit on the same boundary, with
10
- * no shared lifecycle. One subscription, many handlers, no coordination
11
- * via the global event subsystem.
12
- *
13
- * Both events can fire (visibilitychange first when the user navigates
14
- * away, then sometimes beforeunload). Handlers must be idempotent.
15
- */
16
2
  type Handler = () => void;
17
3
  declare function onPageHidden(handler: Handler): () => void;
4
+ declare function onPageShown(handler: Handler): () => void;
18
5
  /** Test hook — drops every handler and unbinds the listeners. */
19
6
  declare function _resetPageLifecycleForTests(): void;
20
7
  //#endregion
21
- export { _resetPageLifecycleForTests, onPageHidden };
8
+ export { _resetPageLifecycleForTests, onPageHidden, onPageShown };
@@ -1 +1 @@
1
- {"version":3,"file":"page-lifecycle.d.mts","names":[],"sources":["../../src/internal/page-lifecycle.ts"],"mappings":";;;;;;AAcY;AA6BZ;;;;AAA6C;AAS7C;;;KAtCK,OAAA;AAAA,iBA6BW,YAAA,CAAa,OAAgB,EAAP,OAAO;;iBAS7B,2BAAA,CAAA"}
1
+ {"version":3,"file":"page-lifecycle.d.mts","names":[],"sources":["../../src/internal/page-lifecycle.ts"],"mappings":";KACK,OAAA;AAAA,iBA0CW,YAAA,CAAa,OAAgB,EAAP,OAAO;AAAA,iBAQ7B,WAAA,CAAY,OAAgB,EAAP,OAAO;;iBAS5B,2BAAA"}
@@ -1 +1 @@
1
- const handlers=new Set;let installed=!1;function fire(){for(let handler of handlers)handler()}function onVisibilityChange(){typeof document<`u`&&document.visibilityState===`hidden`&&fire()}function install(){installed||typeof globalThis.addEventListener!=`function`||(installed=!0,globalThis.addEventListener(`visibilitychange`,onVisibilityChange),globalThis.addEventListener(`beforeunload`,fire))}function onPageHidden(handler){return install(),handlers.add(handler),()=>{handlers.delete(handler)}}function _resetPageLifecycleForTests(){handlers.clear(),installed&&typeof globalThis.removeEventListener==`function`&&(globalThis.removeEventListener(`visibilitychange`,onVisibilityChange),globalThis.removeEventListener(`beforeunload`,fire)),installed=!1}export{_resetPageLifecycleForTests,onPageHidden};
1
+ const hiddenHandlers=new Set,shownHandlers=new Set;let installed=!1;function fire(set){for(let handler of set)handler()}function fireHidden(){fire(hiddenHandlers)}function fireShown(){fire(shownHandlers)}function onVisibilityChange(){if(!(typeof document>`u`)){if(document.visibilityState===`hidden`){fireHidden();return}document.visibilityState===`visible`&&fireShown()}}function install(){installed||typeof globalThis.addEventListener!=`function`||(installed=!0,globalThis.addEventListener(`visibilitychange`,onVisibilityChange),globalThis.addEventListener(`beforeunload`,fireHidden))}function onPageHidden(handler){return install(),hiddenHandlers.add(handler),()=>{hiddenHandlers.delete(handler)}}function onPageShown(handler){return install(),shownHandlers.add(handler),()=>{shownHandlers.delete(handler)}}function _resetPageLifecycleForTests(){hiddenHandlers.clear(),shownHandlers.clear(),installed&&typeof globalThis.removeEventListener==`function`&&(globalThis.removeEventListener(`visibilitychange`,onVisibilityChange),globalThis.removeEventListener(`beforeunload`,fireHidden)),installed=!1}export{_resetPageLifecycleForTests,onPageHidden,onPageShown};
@@ -1 +1 @@
1
- {"version":3,"file":"page-lifecycle.mjs","names":[],"sources":["../../src/internal/page-lifecycle.ts"],"sourcesContent":["/**\n * Single subscription point for `visibilitychange` (hidden) and\n * `beforeunload`. Modules call `onPageHidden(handler)` and the handler\n * fires once per transition into hidden / unload.\n *\n * Replaces the previous shape where the kernel, the queue, the pages\n * plugin, and the replay plugin each attached their own listeners — four\n * subscribers racing to flush / drain / emit on the same boundary, with\n * no shared lifecycle. One subscription, many handlers, no coordination\n * via the global event subsystem.\n *\n * Both events can fire (visibilitychange first when the user navigates\n * away, then sometimes beforeunload). Handlers must be idempotent.\n */\ntype Handler = () => void;\n\nconst handlers = new Set<Handler>();\nlet installed = false;\n\nfunction fire(): void {\n for (const handler of handlers) {\n handler();\n }\n}\n\nfunction onVisibilityChange(): void {\n if (\n typeof document !== \"undefined\" &&\n document.visibilityState === \"hidden\"\n ) {\n fire();\n }\n}\n\nfunction install(): void {\n if (installed || typeof globalThis.addEventListener !== \"function\") {\n return;\n }\n installed = true;\n globalThis.addEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.addEventListener(\"beforeunload\", fire);\n}\n\nexport function onPageHidden(handler: Handler): () => void {\n install();\n handlers.add(handler);\n return () => {\n handlers.delete(handler);\n };\n}\n\n/** Test hook — drops every handler and unbinds the listeners. */\nexport function _resetPageLifecycleForTests(): void {\n handlers.clear();\n if (installed && typeof globalThis.removeEventListener === \"function\") {\n globalThis.removeEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.removeEventListener(\"beforeunload\", fire);\n }\n installed = false;\n}\n"],"mappings":"AAgBA,MAAM,SAAW,IAAI,IACrB,IAAI,UAAY,GAEhB,SAAS,MAAa,CACpB,IAAK,IAAM,WAAW,SACpB,QAAQ,CAEZ,CAEA,SAAS,oBAA2B,CAEhC,OAAO,SAAa,KACpB,SAAS,kBAAoB,UAE7B,KAAK,CAET,CAEA,SAAS,SAAgB,CACnB,WAAa,OAAO,WAAW,kBAAqB,aAGxD,UAAY,GACZ,WAAW,iBAAiB,mBAAoB,kBAAkB,EAClE,WAAW,iBAAiB,eAAgB,IAAI,EAClD,CAEA,SAAgB,aAAa,QAA8B,CAGzD,OAFA,QAAQ,EACR,SAAS,IAAI,OAAO,MACP,CACX,SAAS,OAAO,OAAO,CACzB,CACF,CAGA,SAAgB,6BAAoC,CAClD,SAAS,MAAM,EACX,WAAa,OAAO,WAAW,qBAAwB,aACzD,WAAW,oBAAoB,mBAAoB,kBAAkB,EACrE,WAAW,oBAAoB,eAAgB,IAAI,GAErD,UAAY,EACd"}
1
+ {"version":3,"file":"page-lifecycle.mjs","names":[],"sources":["../../src/internal/page-lifecycle.ts"],"sourcesContent":["// visibilitychange and beforeunload can both fire on hide — handlers must be idempotent.\ntype Handler = () => void;\n\nconst hiddenHandlers = new Set<Handler>();\nconst shownHandlers = new Set<Handler>();\nlet installed = false;\n\nfunction fire(set: Set<Handler>): void {\n for (const handler of set) {\n handler();\n }\n}\n\nfunction fireHidden(): void {\n fire(hiddenHandlers);\n}\n\nfunction fireShown(): void {\n fire(shownHandlers);\n}\n\nfunction onVisibilityChange(): void {\n if (typeof document === \"undefined\") {\n return;\n }\n if (document.visibilityState === \"hidden\") {\n fireHidden();\n return;\n }\n if (document.visibilityState === \"visible\") {\n fireShown();\n }\n}\n\nfunction install(): void {\n if (installed || typeof globalThis.addEventListener !== \"function\") {\n return;\n }\n installed = true;\n globalThis.addEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.addEventListener(\"beforeunload\", fireHidden);\n}\n\nexport function onPageHidden(handler: Handler): () => void {\n install();\n hiddenHandlers.add(handler);\n return () => {\n hiddenHandlers.delete(handler);\n };\n}\n\nexport function onPageShown(handler: Handler): () => void {\n install();\n shownHandlers.add(handler);\n return () => {\n shownHandlers.delete(handler);\n };\n}\n\n/** Test hook — drops every handler and unbinds the listeners. */\nexport function _resetPageLifecycleForTests(): void {\n hiddenHandlers.clear();\n shownHandlers.clear();\n if (installed && typeof globalThis.removeEventListener === \"function\") {\n globalThis.removeEventListener(\"visibilitychange\", onVisibilityChange);\n globalThis.removeEventListener(\"beforeunload\", fireHidden);\n }\n installed = false;\n}\n"],"mappings":"AAGA,MAAM,eAAiB,IAAI,IACrB,cAAgB,IAAI,IAC1B,IAAI,UAAY,GAEhB,SAAS,KAAK,IAAyB,CACrC,IAAK,IAAM,WAAW,IACpB,QAAQ,CAEZ,CAEA,SAAS,YAAmB,CAC1B,KAAK,cAAc,CACrB,CAEA,SAAS,WAAkB,CACzB,KAAK,aAAa,CACpB,CAEA,SAAS,oBAA2B,CAC9B,YAAO,SAAa,KAGxB,IAAI,SAAS,kBAAoB,SAAU,CACzC,WAAW,EACX,MACF,CACI,SAAS,kBAAoB,WAC/B,UAAU,CAFZ,CAIF,CAEA,SAAS,SAAgB,CACnB,WAAa,OAAO,WAAW,kBAAqB,aAGxD,UAAY,GACZ,WAAW,iBAAiB,mBAAoB,kBAAkB,EAClE,WAAW,iBAAiB,eAAgB,UAAU,EACxD,CAEA,SAAgB,aAAa,QAA8B,CAGzD,OAFA,QAAQ,EACR,eAAe,IAAI,OAAO,MACb,CACX,eAAe,OAAO,OAAO,CAC/B,CACF,CAEA,SAAgB,YAAY,QAA8B,CAGxD,OAFA,QAAQ,EACR,cAAc,IAAI,OAAO,MACZ,CACX,cAAc,OAAO,OAAO,CAC9B,CACF,CAGA,SAAgB,6BAAoC,CAClD,eAAe,MAAM,EACrB,cAAc,MAAM,EAChB,WAAa,OAAO,WAAW,qBAAwB,aACzD,WAAW,oBAAoB,mBAAoB,kBAAkB,EACrE,WAAW,oBAAoB,eAAgB,UAAU,GAE3D,UAAY,EACd"}
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-runtime.d.mts","names":[],"sources":["../../src/internal/plugin-runtime.ts"],"mappings":";;;;;;cAwBa,aAAA;EAAA,iBACM,cAAA;EAAA,iBACA,OAAA;EAAA,iBACA,OAAA;EAAA,iBACA,QAAA;EAAA,QACT,YAAA;EAAA,QACA,YAAA;EAAA,QACA,WAAA;EAAA,QACA,QAAA;cAGN,OAAA,EAAS,aAAA,EACT,SAAA,EAAW,eAAA,cACX,cAAA,EAAgB,YAAA;EAOlB,UAAA,CAAA,GAAc,YAAA;EAId,UAAA,CAAW,WAAA,GAAc,YAAA;EAUzB,YAAA,CAAA;EASA,iBAAA,CAAkB,MAAA,EAAQ,kBAAA;EAK1B,KAAA,CAAA;EAWM,OAAA,CAAA,GAAW,OAAA;EAAA,QAWT,kBAAA;EAAA,QAQA,UAAA;EAAA,QAeM,QAAA;EAAA,QAoBN,IAAA;AAAA"}
1
+ {"version":3,"file":"plugin-runtime.d.mts","names":[],"sources":["../../src/internal/plugin-runtime.ts"],"mappings":";;;;;;cAwBa,aAAA;EAAA,iBACM,cAAA;EAAA,iBACA,OAAA;EAAA,iBACA,OAAA;EAAA,iBACA,QAAA;EAAA,QACT,YAAA;EAAA,QACA,YAAA;EAAA,QACA,WAAA;EAAA,QACA,QAAA;cAGN,OAAA,EAAS,aAAA,EACT,SAAA,EAAW,eAAA,cACX,cAAA,EAAgB,YAAA;EAOlB,UAAA,IAAc,YAAA;EAId,UAAA,CAAW,WAAA,GAAc,YAAA;EAUzB,YAAA;EASA,iBAAA,CAAkB,MAAA,EAAQ,kBAAA;EAK1B,KAAA;EAWM,OAAA,IAAW,OAAA;EAAA,QAWT,kBAAA;EAAA,QAQA,UAAA;EAAA,QAeM,QAAA;EAAA,QAoBN,IAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"sw.mjs","names":[],"sources":["../../src/internal/sw.ts"],"sourcesContent":["import { resolveRoutePrefix } from \"@interfere/constants/route-prefix\";\n\nimport { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"sw\");\n\nexport interface SwRegistrationOptions {\n /**\n * Optional hook to bridge SW → kernel telemetry. The SW posts\n * `{ type: \"interfere.sw.queued\" | ... }` messages back to the\n * page when it queues, replays, or drops a request; if provided,\n * this fires for each one so the kernel can record an OTel span\n * event.\n */\n onMessage?: (message: SwMessage) => void;\n}\n\nexport interface SwMessage {\n reason?: string;\n type:\n | \"interfere.sw.queued\"\n | \"interfere.sw.replayed\"\n | \"interfere.sw.dropped\";\n url: string;\n}\n\n/**\n * Best-effort: never throws, never blocks. SW registration is a\n * page-load-time durability bonus; if it fails, the SDK falls back to\n * direct fetch and behaves exactly like 9.x did.\n */\nexport async function registerServiceWorker(\n opts: SwRegistrationOptions = {}\n): Promise<void> {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return;\n }\n\n // Customers can override the proxy mount point via\n // `NEXT_PUBLIC_INTERFERE_ROUTE_PREFIX` (homepage / dashboard both do\n // this). Hardcoding `/api/interfere/sw` would 404 on those surfaces\n // and Chrome stalls the install handler when the SW script can't be\n // fetched, so we resolve the same prefix the rest of the SDK uses.\n const prefix = resolveRoutePrefix();\n const swPath = `${prefix}/sw`;\n const swScope = `${prefix}/`;\n\n try {\n await navigator.serviceWorker.register(swPath, { scope: swScope });\n log.debug(\"registered at %s (scope %s)\", swPath, swScope);\n } catch (err) {\n log.warn(\"registration failed, falling back to direct fetch\", err);\n return;\n }\n\n if (opts.onMessage) {\n const handler = (event: MessageEvent<unknown>) => {\n const data = event.data;\n if (!isSwMessage(data)) {\n return;\n }\n opts.onMessage?.(data);\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n }\n}\n\nfunction isSwMessage(value: unknown): value is SwMessage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const v = value as Record<string, unknown>;\n return (\n (v[\"type\"] === \"interfere.sw.queued\" ||\n v[\"type\"] === \"interfere.sw.replayed\" ||\n v[\"type\"] === \"interfere.sw.dropped\") &&\n typeof v[\"url\"] === \"string\"\n );\n}\n"],"mappings":"4GAIA,MAAM,IAAM,aAAa,IAAI,EA2B7B,eAAsB,sBACpB,KAA8B,CAAC,EAChB,CACf,GAAI,OAAO,UAAc,KAAe,EAAE,kBAAmB,WAC3D,OAQF,IAAM,OAAS,mBAAmB,EAC5B,OAAS,GAAG,OAAO,KACnB,QAAU,GAAG,OAAO,GAE1B,GAAI,CACF,MAAM,UAAU,cAAc,SAAS,OAAQ,CAAE,MAAO,OAAQ,CAAC,EACjE,IAAI,MAAM,8BAA+B,OAAQ,OAAO,CAC1D,OAAS,IAAK,CACZ,IAAI,KAAK,oDAAqD,GAAG,EACjE,MACF,CAEI,KAAK,WAQP,UAAU,cAAc,iBAAiB,UAPxB,OAAiC,CAChD,IAAM,KAAO,MAAM,KACd,YAAY,IAAI,GAGrB,KAAK,YAAY,IAAI,CACvB,CAC2D,CAE/D,CAEA,SAAS,YAAY,MAAoC,CACvD,GAAI,CAAC,OAAS,OAAO,OAAU,SAC7B,MAAO,GAET,IAAM,EAAI,MACV,OACG,EAAE,OAAY,uBACb,EAAE,OAAY,yBACd,EAAE,OAAY,yBAChB,OAAO,EAAE,KAAW,QAExB"}
1
+ {"version":3,"file":"sw.mjs","names":[],"sources":["../../src/internal/sw.ts"],"sourcesContent":["import { resolveRoutePrefix } from \"@interfere/constants/route-prefix\";\n\nimport { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"sw\");\n\nexport interface SwRegistrationOptions {\n /**\n * Optional hook to bridge SW → kernel telemetry. The SW posts\n * `{ type: \"interfere.sw.queued\" | ... }` messages back to the\n * page when it queues, replays, or drops a request; if provided,\n * this fires for each one so the kernel can record an OTel span\n * event.\n */\n onMessage?: (message: SwMessage) => void;\n}\n\nexport interface SwMessage {\n reason?: string;\n type:\n | \"interfere.sw.queued\"\n | \"interfere.sw.replayed\"\n | \"interfere.sw.dropped\";\n url: string;\n}\n\n/**\n * Best-effort: never throws, never blocks. SW registration is a\n * page-load-time durability bonus; if it fails, the SDK falls back to\n * direct fetch and behaves exactly like 9.x did.\n */\nexport async function registerServiceWorker(\n opts: SwRegistrationOptions = {}\n): Promise<void> {\n if (typeof navigator === \"undefined\" || !(\"serviceWorker\" in navigator)) {\n return;\n }\n\n const prefix = resolveRoutePrefix();\n const swPath = `${prefix}/sw`;\n const swScope = `${prefix}/`;\n\n try {\n await navigator.serviceWorker.register(swPath, { scope: swScope });\n log.debug(\"registered at %s (scope %s)\", swPath, swScope);\n } catch (err) {\n log.warn(\"registration failed, falling back to direct fetch\", err);\n return;\n }\n\n if (opts.onMessage) {\n const handler = (event: MessageEvent<unknown>) => {\n const data = event.data;\n if (!isSwMessage(data)) {\n return;\n }\n opts.onMessage?.(data);\n };\n navigator.serviceWorker.addEventListener(\"message\", handler);\n }\n}\n\nfunction isSwMessage(value: unknown): value is SwMessage {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n const v = value as Record<string, unknown>;\n return (\n (v[\"type\"] === \"interfere.sw.queued\" ||\n v[\"type\"] === \"interfere.sw.replayed\" ||\n v[\"type\"] === \"interfere.sw.dropped\") &&\n typeof v[\"url\"] === \"string\"\n );\n}\n"],"mappings":"4GAIA,MAAM,IAAM,aAAa,IAAI,EA2B7B,eAAsB,sBACpB,KAA8B,CAAC,EAChB,CACf,GAAI,OAAO,UAAc,KAAe,EAAE,kBAAmB,WAC3D,OAGF,IAAM,OAAS,mBAAmB,EAC5B,OAAS,GAAG,OAAO,KACnB,QAAU,GAAG,OAAO,GAE1B,GAAI,CACF,MAAM,UAAU,cAAc,SAAS,OAAQ,CAAE,MAAO,OAAQ,CAAC,EACjE,IAAI,MAAM,8BAA+B,OAAQ,OAAO,CAC1D,OAAS,IAAK,CACZ,IAAI,KAAK,oDAAqD,GAAG,EACjE,MACF,CAEI,KAAK,WAQP,UAAU,cAAc,iBAAiB,UAPxB,OAAiC,CAChD,IAAM,KAAO,MAAM,KACd,YAAY,IAAI,GAGrB,KAAK,YAAY,IAAI,CACvB,CAC2D,CAE/D,CAEA,SAAS,YAAY,MAAoC,CACvD,GAAI,CAAC,OAAS,OAAO,OAAU,SAC7B,MAAO,GAET,IAAM,EAAI,MACV,OACG,EAAE,OAAY,uBACb,EAAE,OAAY,yBACd,EAAE,OAAY,yBAChB,OAAO,EAAE,KAAW,QAExB"}
package/dist/package.mjs CHANGED
@@ -1 +1 @@
1
- var name=`@interfere/react`,version=`10.0.1-canary.1`;export{name,version};
1
+ var name=`@interfere/react`,version=`11.0.0-canary.2`;export{name,version};
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/plugins/errors.ts"],"mappings":";;;cAwDa,YAAA,EAAc,MA0F1B"}
1
+ {"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/plugins/errors.ts"],"mappings":";;;cAwDa,YAAA,EAAc,MAgF1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"errors.mjs","names":[],"sources":["../../src/plugins/errors.ts"],"sourcesContent":["import type { IngestedFrame } from \"@interfere/types/data/frame\";\nimport {\n isNonErrorException,\n MECHANISM_TYPE,\n type NonErrorException,\n toException,\n} from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { onConsoleCall } from \"../internal/console-patch.js\";\nimport type { Plugin, PluginContext } from \"./lib/types.js\";\n\n/**\n * V8's default stack limit is 10. Deep React trees routinely exceed that\n * before reaching the actual application frames, leaving only react-dom\n * internals in the captured stack. Matches Sentry's browser SDK.\n */\nconst STACK_TRACE_LIMIT = 50;\n\ninterface CaptureOpts {\n readonly error: Error | NonErrorException;\n /**\n * Fallback frame to inject when the parsed stack of the root exception is\n * empty. Used for `window.onerror` calls where the browser provides\n * `source`/`line`/`col` even though the Error object itself has a\n * degenerate stack. Ignored for non-Error captures — there's no stack\n * to fall back into.\n */\n readonly fallbackFrame?: IngestedFrame;\n readonly mechanism: ErrorMechanism;\n}\n\nfunction capture(ctx: PluginContext, opts: CaptureOpts) {\n ctx.recordException(opts.error, {\n mechanism: opts.mechanism,\n ...(opts.fallbackFrame && !isNonErrorException(opts.error)\n ? { fallbackFrames: [opts.fallbackFrame] }\n : {}),\n });\n}\n\n/**\n * Finds the first `Error` instance in a list of `console.error` arguments.\n * React 18+ in production logs errors as\n * `console.error(\"The above error occurred in ...\", error, componentStack)`,\n * so scanning only `args[0]` misses React's own uncaught-error reports.\n */\nfunction findErrorArg(args: readonly unknown[]): Error | null {\n for (const arg of args) {\n if (arg instanceof Error) {\n return arg;\n }\n }\n return null;\n}\n\nexport const errorsPlugin: Plugin = {\n name: \"errors\",\n\n setup(ctx) {\n const originalOnError = globalThis.onerror;\n const originalStackTraceLimit = Error.stackTraceLimit;\n if (Error.stackTraceLimit < STACK_TRACE_LIMIT) {\n Error.stackTraceLimit = STACK_TRACE_LIMIT;\n }\n\n globalThis.onerror = (msg, source, line, col, error) => {\n if (error instanceof Error) {\n const fallbackFrame =\n typeof source === \"string\"\n ? {\n id: \"fallback-0\",\n file: source,\n ...(typeof line === \"number\" ? { line } : {}),\n ...(typeof col === \"number\" ? { column: col } : {}),\n }\n : null;\n\n capture(ctx, {\n error,\n mechanism: { type: MECHANISM_TYPE.browser.onerror, handled: false },\n ...(fallbackFrame ? { fallbackFrame } : {}),\n });\n }\n if (typeof originalOnError === \"function\") {\n return originalOnError.call(globalThis, msg, source, line, col, error);\n }\n return false;\n };\n\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n // `toException` returns a real Error when the rejection carries a\n // recoverable one (direct Error, nested Error inside an object, or\n // a plain string), or a `NonErrorException` carrying the original\n // structured payload otherwise. The `synthetic: true` flag was\n // historically a \"we lied about the shape\" tag; with structured\n // capture it now means \"this isn't a JS Error\" — same intent,\n // honest signal.\n capture(ctx, {\n error: toException(event.reason),\n mechanism: {\n type: MECHANISM_TYPE.browser.onunhandledrejection,\n handled: false,\n ...(event.reason instanceof Error ? {} : { synthetic: true }),\n },\n });\n };\n // Some non-browser runtimes (notably Node test forks vitest spawns under\n // `pool: \"forks\"`) don't expose `addEventListener` on globalThis. Skip the\n // unhandledrejection hook there rather than crashing plugin setup; the\n // browser path is unaffected.\n const supportsEventTarget =\n typeof globalThis.addEventListener === \"function\" &&\n typeof globalThis.removeEventListener === \"function\";\n if (supportsEventTarget) {\n globalThis.addEventListener(\"unhandledrejection\", onUnhandledRejection);\n }\n\n const unsubscribeConsole = onConsoleCall((level, args) => {\n if (level !== \"error\") {\n return;\n }\n const error = findErrorArg(args);\n if (error) {\n capture(ctx, {\n error,\n mechanism: {\n type: MECHANISM_TYPE.browser.consoleError,\n handled: true,\n },\n });\n }\n });\n\n return () => {\n Error.stackTraceLimit = originalStackTraceLimit;\n globalThis.onerror = originalOnError;\n if (supportsEventTarget) {\n globalThis.removeEventListener(\n \"unhandledrejection\",\n onUnhandledRejection\n );\n }\n unsubscribeConsole();\n };\n },\n};\n\nexport default errorsPlugin;\n"],"mappings":"iJAgCA,SAAS,QAAQ,IAAoB,KAAmB,CACtD,IAAI,gBAAgB,KAAK,MAAO,CAC9B,UAAW,KAAK,UAChB,GAAI,KAAK,eAAiB,CAAC,oBAAoB,KAAK,KAAK,EACrD,CAAE,eAAgB,CAAC,KAAK,aAAa,CAAE,EACvC,CAAC,CACP,CAAC,CACH,CAQA,SAAS,aAAa,KAAwC,CAC5D,IAAK,IAAM,OAAO,KAChB,GAAI,eAAe,MACjB,OAAO,IAGX,OAAO,IACT,CAEA,MAAa,aAAuB,CAClC,KAAM,SAEN,MAAM,IAAK,CACT,IAAM,gBAAkB,WAAW,QAC7B,wBAA0B,MAAM,gBAClC,MAAM,gBAAkB,KAC1B,MAAM,gBAAkB,IAG1B,WAAW,SAAW,IAAK,OAAQ,KAAM,IAAK,QAAU,CACtD,GAAI,iBAAiB,MAAO,CAC1B,IAAM,cACJ,OAAO,QAAW,SACd,CACE,GAAI,aACJ,KAAM,OACN,GAAI,OAAO,MAAS,SAAW,CAAE,IAAK,EAAI,CAAC,EAC3C,GAAI,OAAO,KAAQ,SAAW,CAAE,OAAQ,GAAI,EAAI,CAAC,CACnD,EACA,KAEN,QAAQ,IAAK,CACX,MACA,UAAW,CAAE,KAAM,eAAe,QAAQ,QAAS,QAAS,EAAM,EAClE,GAAI,cAAgB,CAAE,aAAc,EAAI,CAAC,CAC3C,CAAC,CACH,CAIA,OAHI,OAAO,iBAAoB,WACtB,gBAAgB,KAAK,WAAY,IAAK,OAAQ,KAAM,IAAK,KAAK,EAEhE,EACT,EAEA,IAAM,qBAAwB,OAAiC,CAQ7D,QAAQ,IAAK,CACX,MAAO,YAAY,MAAM,MAAM,EAC/B,UAAW,CACT,KAAM,eAAe,QAAQ,qBAC7B,QAAS,GACT,GAAI,MAAM,kBAAkB,MAAQ,CAAC,EAAI,CAAE,UAAW,EAAK,CAC7D,CACF,CAAC,CACH,EAKM,oBACJ,OAAO,WAAW,kBAAqB,YACvC,OAAO,WAAW,qBAAwB,WACxC,qBACF,WAAW,iBAAiB,qBAAsB,oBAAoB,EAGxE,IAAM,mBAAqB,eAAe,MAAO,OAAS,CACxD,GAAI,QAAU,QACZ,OAEF,IAAM,MAAQ,aAAa,IAAI,EAC3B,OACF,QAAQ,IAAK,CACX,MACA,UAAW,CACT,KAAM,eAAe,QAAQ,aAC7B,QAAS,EACX,CACF,CAAC,CAEL,CAAC,EAED,UAAa,CACX,MAAM,gBAAkB,wBACxB,WAAW,QAAU,gBACjB,qBACF,WAAW,oBACT,qBACA,oBACF,EAEF,mBAAmB,CACrB,CACF,CACF"}
1
+ {"version":3,"file":"errors.mjs","names":[],"sources":["../../src/plugins/errors.ts"],"sourcesContent":["import type { IngestedFrame } from \"@interfere/types/data/frame\";\nimport {\n isNonErrorException,\n MECHANISM_TYPE,\n type NonErrorException,\n toException,\n} from \"@interfere/types/sdk/errors\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\n\nimport { onConsoleCall } from \"../internal/console-patch.js\";\nimport type { Plugin, PluginContext } from \"./lib/types.js\";\n\n/**\n * V8's default stack limit is 10. Deep React trees routinely exceed that\n * before reaching the actual application frames, leaving only react-dom\n * internals in the captured stack. Matches Sentry's browser SDK.\n */\nconst STACK_TRACE_LIMIT = 50;\n\ninterface CaptureOpts {\n readonly error: Error | NonErrorException;\n /**\n * Fallback frame to inject when the parsed stack of the root exception is\n * empty. Used for `window.onerror` calls where the browser provides\n * `source`/`line`/`col` even though the Error object itself has a\n * degenerate stack. Ignored for non-Error captures — there's no stack\n * to fall back into.\n */\n readonly fallbackFrame?: IngestedFrame;\n readonly mechanism: ErrorMechanism;\n}\n\nfunction capture(ctx: PluginContext, opts: CaptureOpts) {\n ctx.recordException(opts.error, {\n mechanism: opts.mechanism,\n ...(opts.fallbackFrame && !isNonErrorException(opts.error)\n ? { fallbackFrames: [opts.fallbackFrame] }\n : {}),\n });\n}\n\n/**\n * Finds the first `Error` instance in a list of `console.error` arguments.\n * React 18+ in production logs errors as\n * `console.error(\"The above error occurred in ...\", error, componentStack)`,\n * so scanning only `args[0]` misses React's own uncaught-error reports.\n */\nfunction findErrorArg(args: readonly unknown[]): Error | null {\n for (const arg of args) {\n if (arg instanceof Error) {\n return arg;\n }\n }\n return null;\n}\n\nexport const errorsPlugin: Plugin = {\n name: \"errors\",\n\n setup(ctx) {\n const originalOnError = globalThis.onerror;\n const originalStackTraceLimit = Error.stackTraceLimit;\n if (Error.stackTraceLimit < STACK_TRACE_LIMIT) {\n Error.stackTraceLimit = STACK_TRACE_LIMIT;\n }\n\n globalThis.onerror = (msg, source, line, col, error) => {\n if (error instanceof Error) {\n const fallbackFrame =\n typeof source === \"string\"\n ? {\n id: \"fallback-0\",\n file: source,\n ...(typeof line === \"number\" ? { line } : {}),\n ...(typeof col === \"number\" ? { column: col } : {}),\n }\n : null;\n\n capture(ctx, {\n error,\n mechanism: { type: MECHANISM_TYPE.browser.onerror, handled: false },\n ...(fallbackFrame ? { fallbackFrame } : {}),\n });\n }\n if (typeof originalOnError === \"function\") {\n return originalOnError.call(globalThis, msg, source, line, col, error);\n }\n return false;\n };\n\n const onUnhandledRejection = (event: PromiseRejectionEvent) => {\n capture(ctx, {\n error: toException(event.reason),\n mechanism: {\n type: MECHANISM_TYPE.browser.onunhandledrejection,\n handled: false,\n ...(event.reason instanceof Error ? {} : { synthetic: true }),\n },\n });\n };\n\n const supportsEventTarget =\n typeof globalThis.addEventListener === \"function\" &&\n typeof globalThis.removeEventListener === \"function\";\n if (supportsEventTarget) {\n globalThis.addEventListener(\"unhandledrejection\", onUnhandledRejection);\n }\n\n const unsubscribeConsole = onConsoleCall((level, args) => {\n if (level !== \"error\") {\n return;\n }\n const error = findErrorArg(args);\n if (error) {\n capture(ctx, {\n error,\n mechanism: {\n type: MECHANISM_TYPE.browser.consoleError,\n handled: true,\n },\n });\n }\n });\n\n return () => {\n Error.stackTraceLimit = originalStackTraceLimit;\n globalThis.onerror = originalOnError;\n if (supportsEventTarget) {\n globalThis.removeEventListener(\n \"unhandledrejection\",\n onUnhandledRejection\n );\n }\n unsubscribeConsole();\n };\n },\n};\n\nexport default errorsPlugin;\n"],"mappings":"iJAgCA,SAAS,QAAQ,IAAoB,KAAmB,CACtD,IAAI,gBAAgB,KAAK,MAAO,CAC9B,UAAW,KAAK,UAChB,GAAI,KAAK,eAAiB,CAAC,oBAAoB,KAAK,KAAK,EACrD,CAAE,eAAgB,CAAC,KAAK,aAAa,CAAE,EACvC,CAAC,CACP,CAAC,CACH,CAQA,SAAS,aAAa,KAAwC,CAC5D,IAAK,IAAM,OAAO,KAChB,GAAI,eAAe,MACjB,OAAO,IAGX,OAAO,IACT,CAEA,MAAa,aAAuB,CAClC,KAAM,SAEN,MAAM,IAAK,CACT,IAAM,gBAAkB,WAAW,QAC7B,wBAA0B,MAAM,gBAClC,MAAM,gBAAkB,KAC1B,MAAM,gBAAkB,IAG1B,WAAW,SAAW,IAAK,OAAQ,KAAM,IAAK,QAAU,CACtD,GAAI,iBAAiB,MAAO,CAC1B,IAAM,cACJ,OAAO,QAAW,SACd,CACE,GAAI,aACJ,KAAM,OACN,GAAI,OAAO,MAAS,SAAW,CAAE,IAAK,EAAI,CAAC,EAC3C,GAAI,OAAO,KAAQ,SAAW,CAAE,OAAQ,GAAI,EAAI,CAAC,CACnD,EACA,KAEN,QAAQ,IAAK,CACX,MACA,UAAW,CAAE,KAAM,eAAe,QAAQ,QAAS,QAAS,EAAM,EAClE,GAAI,cAAgB,CAAE,aAAc,EAAI,CAAC,CAC3C,CAAC,CACH,CAIA,OAHI,OAAO,iBAAoB,WACtB,gBAAgB,KAAK,WAAY,IAAK,OAAQ,KAAM,IAAK,KAAK,EAEhE,EACT,EAEA,IAAM,qBAAwB,OAAiC,CAC7D,QAAQ,IAAK,CACX,MAAO,YAAY,MAAM,MAAM,EAC/B,UAAW,CACT,KAAM,eAAe,QAAQ,qBAC7B,QAAS,GACT,GAAI,MAAM,kBAAkB,MAAQ,CAAC,EAAI,CAAE,UAAW,EAAK,CAC7D,CACF,CAAC,CACH,EAEM,oBACJ,OAAO,WAAW,kBAAqB,YACvC,OAAO,WAAW,qBAAwB,WACxC,qBACF,WAAW,iBAAiB,qBAAsB,oBAAoB,EAGxE,IAAM,mBAAqB,eAAe,MAAO,OAAS,CACxD,GAAI,QAAU,QACZ,OAEF,IAAM,MAAQ,aAAa,IAAI,EAC3B,OACF,QAAQ,IAAK,CACX,MACA,UAAW,CACT,KAAM,eAAe,QAAQ,aAC7B,QAAS,EACX,CACF,CAAC,CAEL,CAAC,EAED,UAAa,CACX,MAAM,gBAAkB,wBACxB,WAAW,QAAU,gBACjB,qBACF,WAAW,oBACT,qBACA,oBACF,EAEF,mBAAmB,CACrB,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.mts","names":[],"sources":["../../../src/plugins/lib/loader.ts"],"mappings":";;;;KA4BY,eAAA,GAAkB,OAAA,CAAQ,MAAA,CAAO,SAAA;AAAA,iBAE7B,eAAA,CACd,SAAA,GAAY,eAAA,GACX,MAAA,CAAO,SAAA;AAAA,iBAUY,UAAA,CACpB,GAAA,EAAK,SAAA,EACL,OAAA,EAAS,aAAA,GACR,OAAA,CAAQ,aAAA"}
1
+ {"version":3,"file":"loader.d.mts","names":[],"sources":["../../../src/plugins/lib/loader.ts"],"mappings":";;;;KAuBY,eAAA,GAAkB,OAAA,CAAQ,MAAA,CAAO,SAAA;AAAA,iBAE7B,eAAA,CACd,SAAA,GAAY,eAAA,GACX,MAAA,CAAO,SAAA;AAAA,iBAUY,UAAA,CACpB,GAAA,EAAK,SAAA,EACL,OAAA,EAAS,aAAA,GACR,OAAA,CAAQ,aAAA"}
@@ -1 +1 @@
1
- import{createLogger}from"../../util/log.mjs";import{PLUGIN_MANIFEST}from"@interfere/types/sdk/plugins/manifest";const log=createLogger(`plugins`),LOADERS={errors:()=>import(`../errors.mjs`),logs:()=>import(`../logs.mjs`),rageClick:()=>import(`../rage-clicks.mjs`),replay:()=>import(`../replay.mjs`)},DEFAULTS=Object.fromEntries(PLUGIN_MANIFEST.map(p=>[p.name,p.defaultEnabled]));function resolveFeatures(overrides){return{...DEFAULTS,...overrides}}function resolvePlugin(mod){return`default`in mod&&typeof mod.default.setup==`function`?mod.default:mod}async function loadPlugin(key,context){let loader=LOADERS[key];if(!loader)return null;try{let cleanup=resolvePlugin(await loader()).setup(context);return log.debug(`loaded %s`,key),typeof cleanup==`function`?cleanup:null}catch{return log.error(`failed to load plugin %s`,key),null}}export{loadPlugin,resolveFeatures};
1
+ import{createLogger}from"../../util/log.mjs";import{PLUGIN_MANIFEST}from"@interfere/types/sdk/plugins/manifest";const log=createLogger(`plugins`),LOADERS={errors:()=>import(`../errors.mjs`),logs:()=>import(`../logs.mjs`),rageClick:()=>import(`../rage-clicks.mjs`),replay:()=>import(`../replay/index.mjs`)},DEFAULTS=Object.fromEntries(PLUGIN_MANIFEST.map(p=>[p.name,p.defaultEnabled]));function resolveFeatures(overrides){return{...DEFAULTS,...overrides}}function resolvePlugin(mod){return`default`in mod&&typeof mod.default.setup==`function`?mod.default:mod}async function loadPlugin(key,context){let loader=LOADERS[key];if(!loader)return null;try{let cleanup=resolvePlugin(await loader()).setup(context);return log.debug(`loaded %s`,key),typeof cleanup==`function`?cleanup:null}catch{return log.error(`failed to load plugin %s`,key),null}}export{loadPlugin,resolveFeatures};
@@ -1 +1 @@
1
- {"version":3,"file":"loader.mjs","names":[],"sources":["../../../src/plugins/lib/loader.ts"],"sourcesContent":["import {\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nimport { createLogger } from \"../../util/log.js\";\nimport type { Plugin, PluginCleanup, PluginContext } from \"./types.js\";\n\nconst log = createLogger(\"plugins\");\n\ntype PluginLoader = () => Promise<{ default: Plugin } | Plugin>;\n\n// `pageEvents` from PLUGIN_MANIFEST has no loader: pageviews / clicks /\n// pageleaves are covered by the OTel `BrowserNavigationInstrumentation` +\n// `UserInteractionInstrumentation` auto-instruments instead. The\n// manifest entry stays for the legacy `/v1/ingest` server-side validators\n// (old SDK 9.x clients still emit these envelopes).\nconst LOADERS: Partial<Record<PluginKey, PluginLoader>> = {\n errors: () => import(\"../errors.js\"),\n logs: () => import(\"../logs.js\"),\n rageClick: () => import(\"../rage-clicks.js\"),\n replay: () => import(\"../replay.js\"),\n};\n\nconst DEFAULTS: Record<PluginKey, boolean> = Object.fromEntries(\n PLUGIN_MANIFEST.map((p) => [p.name, p.defaultEnabled])\n) as Record<PluginKey, boolean>;\n\nexport type PluginOverrides = Partial<Record<PluginKey, boolean>>;\n\nexport function resolveFeatures(\n overrides?: PluginOverrides\n): Record<PluginKey, boolean> {\n return { ...DEFAULTS, ...overrides };\n}\n\nfunction resolvePlugin(mod: { default: Plugin } | Plugin): Plugin {\n return \"default\" in mod && typeof (mod.default as Plugin).setup === \"function\"\n ? mod.default\n : (mod as Plugin);\n}\n\nexport async function loadPlugin(\n key: PluginKey,\n context: PluginContext\n): Promise<PluginCleanup | null> {\n const loader = LOADERS[key];\n if (!loader) {\n return null;\n }\n\n try {\n const mod = await loader();\n const plugin = resolvePlugin(mod);\n const cleanup = plugin.setup(context);\n log.debug(\"loaded %s\", key);\n return typeof cleanup === \"function\" ? cleanup : null;\n } catch {\n log.error(\"failed to load plugin %s\", key);\n return null;\n }\n}\n"],"mappings":"gHAQA,MAAM,IAAM,aAAa,SAAS,EAS5B,QAAoD,CACxD,WAAc,OAAO,iBACrB,SAAY,OAAO,eACnB,cAAiB,OAAO,sBACxB,WAAc,OAAO,gBACvB,EAEM,SAAuC,OAAO,YAClD,gBAAgB,IAAK,GAAM,CAAC,EAAE,KAAM,EAAE,cAAc,CAAC,CACvD,EAIA,SAAgB,gBACd,UAC4B,CAC5B,MAAO,CAAE,GAAG,SAAU,GAAG,SAAU,CACrC,CAEA,SAAS,cAAc,IAA2C,CAChE,MAAO,YAAa,KAAO,OAAQ,IAAI,QAAmB,OAAU,WAChE,IAAI,QACH,GACP,CAEA,eAAsB,WACpB,IACA,QAC+B,CAC/B,IAAM,OAAS,QAAQ,KACvB,GAAI,CAAC,OACH,OAAO,KAGT,GAAI,CAGF,IAAM,QADS,cAAc,MADX,OAAO,CAEJ,EAAE,MAAM,OAAO,EAEpC,OADA,IAAI,MAAM,YAAa,GAAG,EACnB,OAAO,SAAY,WAAa,QAAU,IACnD,MAAQ,CAEN,OADA,IAAI,MAAM,2BAA4B,GAAG,EAClC,IACT,CACF"}
1
+ {"version":3,"file":"loader.mjs","names":[],"sources":["../../../src/plugins/lib/loader.ts"],"sourcesContent":["import {\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nimport { createLogger } from \"../../util/log.js\";\nimport type { Plugin, PluginCleanup, PluginContext } from \"./types.js\";\n\nconst log = createLogger(\"plugins\");\n\ntype PluginLoader = () => Promise<{ default: Plugin } | Plugin>;\n\nconst LOADERS: Partial<Record<PluginKey, PluginLoader>> = {\n errors: () => import(\"../errors.js\"),\n logs: () => import(\"../logs.js\"),\n rageClick: () => import(\"../rage-clicks.js\"),\n replay: () => import(\"../replay/index.js\"),\n};\n\nconst DEFAULTS: Record<PluginKey, boolean> = Object.fromEntries(\n PLUGIN_MANIFEST.map((p) => [p.name, p.defaultEnabled])\n) as Record<PluginKey, boolean>;\n\nexport type PluginOverrides = Partial<Record<PluginKey, boolean>>;\n\nexport function resolveFeatures(\n overrides?: PluginOverrides\n): Record<PluginKey, boolean> {\n return { ...DEFAULTS, ...overrides };\n}\n\nfunction resolvePlugin(mod: { default: Plugin } | Plugin): Plugin {\n return \"default\" in mod && typeof (mod.default as Plugin).setup === \"function\"\n ? mod.default\n : (mod as Plugin);\n}\n\nexport async function loadPlugin(\n key: PluginKey,\n context: PluginContext\n): Promise<PluginCleanup | null> {\n const loader = LOADERS[key];\n if (!loader) {\n return null;\n }\n\n try {\n const mod = await loader();\n const plugin = resolvePlugin(mod);\n const cleanup = plugin.setup(context);\n log.debug(\"loaded %s\", key);\n return typeof cleanup === \"function\" ? cleanup : null;\n } catch {\n log.error(\"failed to load plugin %s\", key);\n return null;\n }\n}\n"],"mappings":"gHAQA,MAAM,IAAM,aAAa,SAAS,EAI5B,QAAoD,CACxD,WAAc,OAAO,iBACrB,SAAY,OAAO,eACnB,cAAiB,OAAO,sBACxB,WAAc,OAAO,sBACvB,EAEM,SAAuC,OAAO,YAClD,gBAAgB,IAAK,GAAM,CAAC,EAAE,KAAM,EAAE,cAAc,CAAC,CACvD,EAIA,SAAgB,gBACd,UAC4B,CAC5B,MAAO,CAAE,GAAG,SAAU,GAAG,SAAU,CACrC,CAEA,SAAS,cAAc,IAA2C,CAChE,MAAO,YAAa,KAAO,OAAQ,IAAI,QAAmB,OAAU,WAChE,IAAI,QACH,GACP,CAEA,eAAsB,WACpB,IACA,QAC+B,CAC/B,IAAM,OAAS,QAAQ,KACvB,GAAI,CAAC,OACH,OAAO,KAGT,GAAI,CAGF,IAAM,QADS,cAAc,MADX,OAAO,CAEJ,EAAE,MAAM,OAAO,EAEpC,OADA,IAAI,MAAM,YAAa,GAAG,EACnB,OAAO,SAAY,WAAa,QAAU,IACnD,MAAQ,CAEN,OADA,IAAI,MAAM,2BAA4B,GAAG,EAClC,IACT,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"rage-clicks.d.mts","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"mappings":";;;cAgCa,gBAAA,EAAkB,MAqE9B"}
1
+ {"version":3,"file":"rage-clicks.d.mts","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"mappings":";;;cAgCa,gBAAA,EAAkB,MA8D9B"}
@@ -1 +1 @@
1
- {"version":3,"file":"rage-clicks.mjs","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"sourcesContent":["import { trace } from \"@opentelemetry/api\";\n\nimport type { Plugin } from \"./lib/types.js\";\n\nconst CLICK_THRESHOLD = 3;\nconst TIME_WINDOW_MS = 800;\nconst PROXIMITY_PX = 30;\nconst TRACER_NAME = \"@interfere/react/rage-clicks\";\n\ninterface Click {\n target: Element | null;\n ts: number;\n x: number;\n y: number;\n}\n\nfunction distance(a: Click, b: Click): number {\n return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);\n}\n\nfunction selectorFor(el: Element | null): string {\n if (!el) {\n return \"unknown\";\n }\n if (el.id) {\n return `#${el.id}`;\n }\n const classes = [...el.classList].slice(0, 3).join(\".\");\n const tag = el.tagName.toLowerCase();\n return classes ? `${tag}.${classes}` : tag;\n}\n\nexport const rageClicksPlugin: Plugin = {\n name: \"rage-clicks\",\n\n setup() {\n const clicks: Click[] = [];\n\n const onClick = (event: MouseEvent) => {\n const now = Date.now();\n clicks.push({\n x: event.clientX,\n y: event.clientY,\n ts: now,\n target: event.target instanceof Element ? event.target : null,\n });\n\n // Prune stale clicks\n while (clicks.length > 0 && now - (clicks[0]?.ts ?? 0) > TIME_WINDOW_MS) {\n clicks.shift();\n }\n\n if (clicks.length < CLICK_THRESHOLD) {\n return;\n }\n\n // Check proximity — all clicks within PROXIMITY_PX of the first\n const anchor = clicks[0];\n if (!anchor) {\n return;\n }\n const clustered = clicks.filter(\n (c) => distance(anchor, c) <= PROXIMITY_PX\n );\n if (clustered.length < CLICK_THRESHOLD) {\n return;\n }\n\n const last = clustered.at(-1);\n if (!last) {\n return;\n }\n\n // Emit as a discrete OTel span. The cluster of clicks is a single\n // user-experience event; a span (rather than a span event on\n // whatever's currently active) keeps the rage-click correlatable\n // by `ui.event_type=rage_click` regardless of whether a navigation\n // / interaction span happens to be open at click time.\n const span = trace.getTracer(TRACER_NAME).startSpan(\"rage_click\", {\n attributes: {\n \"ui.event_type\": \"rage_click\",\n \"ui.rage_click.count\": clustered.length,\n \"ui.rage_click.time_window_ms\": last.ts - anchor.ts,\n \"ui.rage_click.selector\": selectorFor(last.target),\n \"ui.rage_click.text\":\n last.target?.textContent?.trim().slice(0, 120) ?? \"\",\n \"ui.rage_click.x\": last.x,\n \"ui.rage_click.y\": last.y,\n },\n });\n span.end();\n\n clicks.length = 0;\n };\n\n document.addEventListener(\"click\", onClick, { capture: true });\n\n return () => {\n document.removeEventListener(\"click\", onClick, { capture: true });\n };\n },\n};\n\nexport default rageClicksPlugin;\n"],"mappings":"sCAgBA,SAAS,SAAS,EAAU,EAAkB,CAC5C,OAAO,KAAK,MAAM,EAAE,EAAI,EAAE,IAAM,GAAK,EAAE,EAAI,EAAE,IAAM,CAAC,CACtD,CAEA,SAAS,YAAY,GAA4B,CAC/C,GAAI,CAAC,GACH,MAAO,UAET,GAAI,GAAG,GACL,MAAO,IAAI,GAAG,KAEhB,IAAM,QAAU,CAAC,GAAG,GAAG,SAAS,EAAE,MAAM,EAAG,CAAC,EAAE,KAAK,GAAG,EAChD,IAAM,GAAG,QAAQ,YAAY,EACnC,OAAO,QAAU,GAAG,IAAI,GAAG,UAAY,GACzC,CAEA,MAAa,iBAA2B,CACtC,KAAM,cAEN,OAAQ,CACN,IAAM,OAAkB,CAAC,EAEnB,QAAW,OAAsB,CACrC,IAAM,IAAM,KAAK,IAAI,EASrB,IARA,OAAO,KAAK,CACV,EAAG,MAAM,QACT,EAAG,MAAM,QACT,GAAI,IACJ,OAAQ,MAAM,kBAAkB,QAAU,MAAM,OAAS,IAC3D,CAAC,EAGM,OAAO,OAAS,GAAK,KAAO,OAAO,IAAI,IAAM,GAAK,KACvD,OAAO,MAAM,EAGf,GAAI,OAAO,OAAS,EAClB,OAIF,IAAM,OAAS,OAAO,GACtB,GAAI,CAAC,OACH,OAEF,IAAM,UAAY,OAAO,OACtB,GAAM,SAAS,OAAQ,CAAC,GAAK,EAChC,EACA,GAAI,UAAU,OAAS,EACrB,OAGF,IAAM,KAAO,UAAU,GAAG,EAAE,EACvB,OAqBL,MAZmB,UAAU,8BAAW,EAAE,UAAU,aAAc,CAChE,WAAY,CACV,gBAAiB,aACjB,sBAAuB,UAAU,OACjC,+BAAgC,KAAK,GAAK,OAAO,GACjD,yBAA0B,YAAY,KAAK,MAAM,EACjD,qBACE,KAAK,QAAQ,aAAa,KAAK,EAAE,MAAM,EAAG,GAAG,GAAK,GACpD,kBAAmB,KAAK,EACxB,kBAAmB,KAAK,CAC1B,CACF,CACG,EAAE,IAAI,EAET,OAAO,OAAS,EAClB,EAIA,OAFA,SAAS,iBAAiB,QAAS,QAAS,CAAE,QAAS,EAAK,CAAC,MAEhD,CACX,SAAS,oBAAoB,QAAS,QAAS,CAAE,QAAS,EAAK,CAAC,CAClE,CACF,CACF"}
1
+ {"version":3,"file":"rage-clicks.mjs","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"sourcesContent":["import { trace } from \"@opentelemetry/api\";\n\nimport type { Plugin } from \"./lib/types.js\";\n\nconst CLICK_THRESHOLD = 3;\nconst TIME_WINDOW_MS = 800;\nconst PROXIMITY_PX = 30;\nconst TRACER_NAME = \"@interfere/react/rage-clicks\";\n\ninterface Click {\n target: Element | null;\n ts: number;\n x: number;\n y: number;\n}\n\nfunction distance(a: Click, b: Click): number {\n return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);\n}\n\nfunction selectorFor(el: Element | null): string {\n if (!el) {\n return \"unknown\";\n }\n if (el.id) {\n return `#${el.id}`;\n }\n const classes = [...el.classList].slice(0, 3).join(\".\");\n const tag = el.tagName.toLowerCase();\n return classes ? `${tag}.${classes}` : tag;\n}\n\nexport const rageClicksPlugin: Plugin = {\n name: \"rage-clicks\",\n\n setup() {\n const clicks: Click[] = [];\n\n const onClick = (event: MouseEvent) => {\n const now = Date.now();\n clicks.push({\n x: event.clientX,\n y: event.clientY,\n ts: now,\n target: event.target instanceof Element ? event.target : null,\n });\n\n while (clicks.length > 0 && now - (clicks[0]?.ts ?? 0) > TIME_WINDOW_MS) {\n clicks.shift();\n }\n\n if (clicks.length < CLICK_THRESHOLD) {\n return;\n }\n\n const anchor = clicks[0];\n if (!anchor) {\n return;\n }\n const clustered = clicks.filter(\n (c) => distance(anchor, c) <= PROXIMITY_PX\n );\n if (clustered.length < CLICK_THRESHOLD) {\n return;\n }\n\n const last = clustered.at(-1);\n if (!last) {\n return;\n }\n\n const span = trace.getTracer(TRACER_NAME).startSpan(\"rage_click\", {\n attributes: {\n \"ui.event_type\": \"rage_click\",\n \"ui.rage_click.count\": clustered.length,\n \"ui.rage_click.time_window_ms\": last.ts - anchor.ts,\n \"ui.rage_click.selector\": selectorFor(last.target),\n \"ui.rage_click.text\":\n last.target?.textContent?.trim().slice(0, 120) ?? \"\",\n \"ui.rage_click.x\": last.x,\n \"ui.rage_click.y\": last.y,\n },\n });\n span.end();\n\n clicks.length = 0;\n };\n\n document.addEventListener(\"click\", onClick, { capture: true });\n\n return () => {\n document.removeEventListener(\"click\", onClick, { capture: true });\n };\n },\n};\n\nexport default rageClicksPlugin;\n"],"mappings":"sCAgBA,SAAS,SAAS,EAAU,EAAkB,CAC5C,OAAO,KAAK,MAAM,EAAE,EAAI,EAAE,IAAM,GAAK,EAAE,EAAI,EAAE,IAAM,CAAC,CACtD,CAEA,SAAS,YAAY,GAA4B,CAC/C,GAAI,CAAC,GACH,MAAO,UAET,GAAI,GAAG,GACL,MAAO,IAAI,GAAG,KAEhB,IAAM,QAAU,CAAC,GAAG,GAAG,SAAS,EAAE,MAAM,EAAG,CAAC,EAAE,KAAK,GAAG,EAChD,IAAM,GAAG,QAAQ,YAAY,EACnC,OAAO,QAAU,GAAG,IAAI,GAAG,UAAY,GACzC,CAEA,MAAa,iBAA2B,CACtC,KAAM,cAEN,OAAQ,CACN,IAAM,OAAkB,CAAC,EAEnB,QAAW,OAAsB,CACrC,IAAM,IAAM,KAAK,IAAI,EAQrB,IAPA,OAAO,KAAK,CACV,EAAG,MAAM,QACT,EAAG,MAAM,QACT,GAAI,IACJ,OAAQ,MAAM,kBAAkB,QAAU,MAAM,OAAS,IAC3D,CAAC,EAEM,OAAO,OAAS,GAAK,KAAO,OAAO,IAAI,IAAM,GAAK,KACvD,OAAO,MAAM,EAGf,GAAI,OAAO,OAAS,EAClB,OAGF,IAAM,OAAS,OAAO,GACtB,GAAI,CAAC,OACH,OAEF,IAAM,UAAY,OAAO,OACtB,GAAM,SAAS,OAAQ,CAAC,GAAK,EAChC,EACA,GAAI,UAAU,OAAS,EACrB,OAGF,IAAM,KAAO,UAAU,GAAG,EAAE,EACvB,OAgBL,MAZmB,UAAU,8BAAW,EAAE,UAAU,aAAc,CAChE,WAAY,CACV,gBAAiB,aACjB,sBAAuB,UAAU,OACjC,+BAAgC,KAAK,GAAK,OAAO,GACjD,yBAA0B,YAAY,KAAK,MAAM,EACjD,qBACE,KAAK,QAAQ,aAAa,KAAK,EAAE,MAAM,EAAG,GAAG,GAAK,GACpD,kBAAmB,KAAK,EACxB,kBAAmB,KAAK,CAC1B,CACF,CACG,EAAE,IAAI,EAET,OAAO,OAAS,EAClB,EAIA,OAFA,SAAS,iBAAiB,QAAS,QAAS,CAAE,QAAS,EAAK,CAAC,MAEhD,CACX,SAAS,oBAAoB,QAAS,QAAS,CAAE,QAAS,EAAK,CAAC,CAClE,CACF,CACF"}
@@ -0,0 +1,13 @@
1
+ import { BufferedReplayEvent } from "./chunking.mjs";
2
+
3
+ //#region src/plugins/replay/buffer.d.ts
4
+ interface ReplayBuffer {
5
+ drain(): BufferedReplayEvent[];
6
+ push(event: unknown): void;
7
+ }
8
+ declare function createReplayBuffer(opts: {
9
+ onSoftLimit: () => void;
10
+ softLimitBytes: number;
11
+ }): ReplayBuffer;
12
+ //#endregion
13
+ export { ReplayBuffer, createReplayBuffer };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffer.d.mts","names":[],"sources":["../../../src/plugins/replay/buffer.ts"],"mappings":";;;UAEiB,YAAA;EACf,KAAA,IAAS,mBAAmB;EAC5B,IAAA,CAAK,KAAA;AAAA;AAAA,iBAGS,kBAAA,CAAmB,IAAA;EACjC,WAAA;EACA,cAAA;AAAA,IACE,YAAY"}
@@ -0,0 +1 @@
1
+ import{utf8ByteLength}from"./chunking.mjs";function createReplayBuffer(opts){let entries=[],totalBytes=0;return{push(event){let json=JSON.stringify(event),bytes=utf8ByteLength(json);entries.push({json,ts:Date.now(),bytes}),totalBytes+=bytes,totalBytes>=opts.softLimitBytes&&opts.onSoftLimit()},drain(){let drained=entries;return entries=[],totalBytes=0,drained}}}export{createReplayBuffer};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buffer.mjs","names":[],"sources":["../../../src/plugins/replay/buffer.ts"],"sourcesContent":["import { type BufferedReplayEvent, utf8ByteLength } from \"./chunking.js\";\n\nexport interface ReplayBuffer {\n drain(): BufferedReplayEvent[];\n push(event: unknown): void;\n}\n\nexport function createReplayBuffer(opts: {\n onSoftLimit: () => void;\n softLimitBytes: number;\n}): ReplayBuffer {\n let entries: BufferedReplayEvent[] = [];\n let totalBytes = 0;\n\n return {\n push(event) {\n const json = JSON.stringify(event);\n const bytes = utf8ByteLength(json);\n entries.push({ json, ts: Date.now(), bytes });\n totalBytes += bytes;\n if (totalBytes >= opts.softLimitBytes) {\n opts.onSoftLimit();\n }\n },\n\n drain() {\n const drained = entries;\n entries = [];\n totalBytes = 0;\n return drained;\n },\n };\n}\n"],"mappings":"2CAOA,SAAgB,mBAAmB,KAGlB,CACf,IAAI,QAAiC,CAAC,EAClC,WAAa,EAEjB,MAAO,CACL,KAAK,MAAO,CACV,IAAM,KAAO,KAAK,UAAU,KAAK,EAC3B,MAAQ,eAAe,IAAI,EACjC,QAAQ,KAAK,CAAE,KAAM,GAAI,KAAK,IAAI,EAAG,KAAM,CAAC,EAC5C,YAAc,MACV,YAAc,KAAK,gBACrB,KAAK,YAAY,CAErB,EAEA,OAAQ,CACN,IAAM,QAAU,QAGhB,MAFA,SAAU,CAAC,EACX,WAAa,EACN,OACT,CACF,CACF"}
@@ -0,0 +1,21 @@
1
+ //#region src/plugins/replay/chunking.d.ts
2
+ interface BufferedReplayEvent {
3
+ bytes: number;
4
+ json: string;
5
+ ts: number;
6
+ }
7
+ interface ReplayChunkPayload {
8
+ bodyJson: string;
9
+ eventCount: number;
10
+ firstTs: number | null;
11
+ lastTs: number | null;
12
+ }
13
+ interface SplitReplayEventsResult {
14
+ chunks: BufferedReplayEvent[][];
15
+ droppedEventBytes: number[];
16
+ }
17
+ declare function toReplayChunkPayload(group: BufferedReplayEvent[]): ReplayChunkPayload;
18
+ declare function splitReplayEvents(buffered: BufferedReplayEvent[]): SplitReplayEventsResult;
19
+ declare function utf8ByteLength(value: string): number;
20
+ //#endregion
21
+ export { BufferedReplayEvent, ReplayChunkPayload, SplitReplayEventsResult, splitReplayEvents, toReplayChunkPayload, utf8ByteLength };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunking.d.mts","names":[],"sources":["../../../src/plugins/replay/chunking.ts"],"mappings":";UAOiB,mBAAA;EACf,KAAA;EACA,IAAA;EACA,EAAA;AAAA;AAAA,UAGe,kBAAA;EACf,QAAA;EACA,UAAA;EACA,OAAA;EACA,MAAA;AAAA;AAAA,UAGe,uBAAA;EACf,MAAA,EAAQ,mBAAmB;EAC3B,iBAAA;AAAA;AAAA,iBAGc,oBAAA,CACd,KAAA,EAAO,mBAAA,KACN,kBAAkB;AAAA,iBAaL,iBAAA,CACd,QAAA,EAAU,mBAAA,KACT,uBAAuB;AAAA,iBAmCV,cAAA,CAAe,KAAa"}
@@ -0,0 +1 @@
1
+ const textEncoder=new TextEncoder;function toReplayChunkPayload(group){let parts=[];for(let entry of group)parts.push(entry.json);return{bodyJson:`[${parts.join(`,`)}]`,eventCount:group.length,firstTs:group[0]?.ts??null,lastTs:group.at(-1)?.ts??null}}function splitReplayEvents(buffered){let chunks=[],droppedEventBytes=[],current=[],currentBytes=2;for(let entry of buffered){let singleEventBodyBytes=2+entry.bytes;if(singleEventBodyBytes>8388608){droppedEventBytes.push(singleEventBodyBytes);continue}let commaBytes=current.length===0?0:1;current.length>0&&currentBytes+commaBytes+entry.bytes>6291456&&(chunks.push(current),current=[],currentBytes=2),currentBytes+=(current.length===0?0:1)+entry.bytes,current.push(entry)}return current.length>0&&chunks.push(current),{chunks,droppedEventBytes}}function utf8ByteLength(value){return textEncoder.encode(value).length}export{splitReplayEvents,toReplayChunkPayload,utf8ByteLength};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunking.mjs","names":[],"sources":["../../../src/plugins/replay/chunking.ts"],"sourcesContent":["const UPLOAD_LIMIT_BYTES = 8 * 1024 * 1024;\nconst TARGET_CHUNK_BYTES = 6 * 1024 * 1024;\nconst JSON_ARRAY_OVERHEAD_BYTES = 2;\nconst JSON_COMMA_BYTES = 1;\n\nconst textEncoder = new TextEncoder();\n\nexport interface BufferedReplayEvent {\n bytes: number;\n json: string;\n ts: number;\n}\n\nexport interface ReplayChunkPayload {\n bodyJson: string;\n eventCount: number;\n firstTs: number | null;\n lastTs: number | null;\n}\n\nexport interface SplitReplayEventsResult {\n chunks: BufferedReplayEvent[][];\n droppedEventBytes: number[];\n}\n\nexport function toReplayChunkPayload(\n group: BufferedReplayEvent[]\n): ReplayChunkPayload {\n const parts: string[] = [];\n for (const entry of group) {\n parts.push(entry.json);\n }\n return {\n bodyJson: `[${parts.join(\",\")}]`,\n eventCount: group.length,\n firstTs: group[0]?.ts ?? null,\n lastTs: group.at(-1)?.ts ?? null,\n };\n}\n\nexport function splitReplayEvents(\n buffered: BufferedReplayEvent[]\n): SplitReplayEventsResult {\n const chunks: BufferedReplayEvent[][] = [];\n const droppedEventBytes: number[] = [];\n let current: BufferedReplayEvent[] = [];\n let currentBytes = JSON_ARRAY_OVERHEAD_BYTES;\n\n for (const entry of buffered) {\n const singleEventBodyBytes = JSON_ARRAY_OVERHEAD_BYTES + entry.bytes;\n\n if (singleEventBodyBytes > UPLOAD_LIMIT_BYTES) {\n droppedEventBytes.push(singleEventBodyBytes);\n continue;\n }\n\n const commaBytes = current.length === 0 ? 0 : JSON_COMMA_BYTES;\n if (\n current.length > 0 &&\n currentBytes + commaBytes + entry.bytes > TARGET_CHUNK_BYTES\n ) {\n chunks.push(current);\n current = [];\n currentBytes = JSON_ARRAY_OVERHEAD_BYTES;\n }\n\n currentBytes += (current.length === 0 ? 0 : JSON_COMMA_BYTES) + entry.bytes;\n current.push(entry);\n }\n\n if (current.length > 0) {\n chunks.push(current);\n }\n\n return { chunks, droppedEventBytes };\n}\n\nexport function utf8ByteLength(value: string): number {\n return textEncoder.encode(value).length;\n}\n"],"mappings":"AAAA,MAKM,YAAc,IAAI,YAoBxB,SAAgB,qBACd,MACoB,CACpB,IAAM,MAAkB,CAAC,EACzB,IAAK,IAAM,SAAS,MAClB,MAAM,KAAK,MAAM,IAAI,EAEvB,MAAO,CACL,SAAU,IAAI,MAAM,KAAK,GAAG,EAAE,GAC9B,WAAY,MAAM,OAClB,QAAS,MAAM,IAAI,IAAM,KACzB,OAAQ,MAAM,GAAG,EAAE,GAAG,IAAM,IAC9B,CACF,CAEA,SAAgB,kBACd,SACyB,CACzB,IAAM,OAAkC,CAAC,EACnC,kBAA8B,CAAC,EACjC,QAAiC,CAAC,EAClC,aAAe,EAEnB,IAAK,IAAM,SAAS,SAAU,CAC5B,IAAM,qBAAuB,EAA4B,MAAM,MAE/D,GAAI,qBAAuB,QAAoB,CAC7C,kBAAkB,KAAK,oBAAoB,EAC3C,QACF,CAEA,IAAM,WAAa,QAAQ,SAAW,EAAI,EAAI,EAE5C,QAAQ,OAAS,GACjB,aAAe,WAAa,MAAM,MAAQ,UAE1C,OAAO,KAAK,OAAO,EACnB,QAAU,CAAC,EACX,aAAe,GAGjB,eAAiB,QAAQ,SAAW,EAAI,EAAI,GAAoB,MAAM,MACtE,QAAQ,KAAK,KAAK,CACpB,CAMA,OAJI,QAAQ,OAAS,GACnB,OAAO,KAAK,OAAO,EAGd,CAAE,OAAQ,iBAAkB,CACrC,CAEA,SAAgB,eAAe,MAAuB,CACpD,OAAO,YAAY,OAAO,KAAK,EAAE,MACnC"}
@@ -0,0 +1,9 @@
1
+ //#region src/plugins/replay/compressor-host.d.ts
2
+ /** `gzip` is `null` when compression is unavailable — callers must POST the chunk uncompressed. */
3
+ interface Compressor {
4
+ readonly dispose: () => void;
5
+ readonly gzip: ((bytes: Uint8Array) => Promise<Uint8Array<ArrayBuffer>>) | null;
6
+ }
7
+ declare function createCompressor(): Compressor;
8
+ //#endregion
9
+ export { Compressor, createCompressor };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compressor-host.d.mts","names":[],"sources":["../../../src/plugins/replay/compressor-host.ts"],"mappings":";;UAKiB,UAAA;EAAA,SACN,OAAA;EAAA,SACA,IAAA,IACH,KAAA,EAAO,UAAA,KAAe,OAAA,CAAQ,UAAA,CAAW,WAAA;AAAA;AAAA,iBAMjC,gBAAA,IAAoB,UAAU"}
@@ -0,0 +1 @@
1
+ import{createLogger}from"../../util/log.mjs";const log=createLogger(`replay/compressor`),FAIL_OPEN={gzip:null,dispose:()=>void 0};function createCompressor(){return typeof CompressionStream>`u`?(log.warn(`CompressionStream unavailable; replay chunks will POST uncompressed`),FAIL_OPEN):{gzip:async bytes=>{let cs=new CompressionStream(`gzip`),compressed=new Response(cs.readable).arrayBuffer(),writer=cs.writable.getWriter();return await writer.write(new Uint8Array(bytes)),await writer.close(),new Uint8Array(await compressed)},dispose:()=>void 0}}export{createCompressor};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compressor-host.mjs","names":[],"sources":["../../../src/plugins/replay/compressor-host.ts"],"sourcesContent":["import { createLogger } from \"../../util/log.js\";\n\nconst log = createLogger(\"replay/compressor\");\n\n/** `gzip` is `null` when compression is unavailable — callers must POST the chunk uncompressed. */\nexport interface Compressor {\n readonly dispose: () => void;\n readonly gzip:\n | ((bytes: Uint8Array) => Promise<Uint8Array<ArrayBuffer>>)\n | null;\n}\n\nconst FAIL_OPEN: Compressor = { gzip: null, dispose: (): void => undefined };\n\nexport function createCompressor(): Compressor {\n if (typeof CompressionStream === \"undefined\") {\n log.warn(\n \"CompressionStream unavailable; replay chunks will POST uncompressed\"\n );\n return FAIL_OPEN;\n }\n\n const gzip = async (bytes: Uint8Array): Promise<Uint8Array<ArrayBuffer>> => {\n const cs = new CompressionStream(\"gzip\");\n const compressed = new Response(cs.readable).arrayBuffer();\n const writer = cs.writable.getWriter();\n await writer.write(new Uint8Array(bytes));\n await writer.close();\n return new Uint8Array(await compressed);\n };\n\n return { gzip, dispose: (): void => undefined };\n}\n"],"mappings":"6CAEA,MAAM,IAAM,aAAa,mBAAmB,EAUtC,UAAwB,CAAE,KAAM,KAAM,YAAqB,IAAA,EAAU,EAE3E,SAAgB,kBAA+B,CAiB7C,OAhBI,OAAO,kBAAsB,KAC/B,IAAI,KACF,qEACF,EACO,WAYF,CAAE,UATW,QAAwD,CAC1E,IAAM,GAAK,IAAI,kBAAkB,MAAM,EACjC,WAAa,IAAI,SAAS,GAAG,QAAQ,EAAE,YAAY,EACnD,OAAS,GAAG,SAAS,UAAU,EAGrC,OAFA,MAAM,OAAO,MAAM,IAAI,WAAW,KAAK,CAAC,EACxC,MAAM,OAAO,MAAM,EACZ,IAAI,WAAW,MAAM,UAAU,CACxC,EAEe,YAAqB,IAAA,EAAU,CAChD"}
@@ -0,0 +1,23 @@
1
+ import { PluginContext } from "../lib/types.mjs";
2
+ import { ReplayChunkPayload } from "./chunking.mjs";
3
+ import { Compressor } from "./compressor-host.mjs";
4
+
5
+ //#region src/plugins/replay/delivery.d.ts
6
+ interface DeliverInput {
7
+ authHeaders: Record<string, string>;
8
+ compressor: Compressor;
9
+ ctx: PluginContext;
10
+ payload: ReplayChunkPayload;
11
+ skipCompression: boolean;
12
+ uploadBaseUrl: string;
13
+ }
14
+ declare function deliverChunk({
15
+ ctx,
16
+ uploadBaseUrl,
17
+ authHeaders,
18
+ compressor,
19
+ payload,
20
+ skipCompression
21
+ }: DeliverInput): Promise<void>;
22
+ //#endregion
23
+ export { DeliverInput, deliverChunk };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery.d.mts","names":[],"sources":["../../../src/plugins/replay/delivery.ts"],"mappings":";;;;;UAoCiB,YAAA;EACf,WAAA,EAAa,MAAA;EACb,UAAA,EAAY,UAAA;EACZ,GAAA,EAAK,aAAA;EACL,OAAA,EAAS,kBAAA;EACT,eAAA;EACA,aAAA;AAAA;AAAA,iBAGoB,YAAA;EACpB,GAAA;EACA,aAAA;EACA,WAAA;EACA,UAAA;EACA,OAAA;EACA;AAAA,GACC,YAAA,GAAe,OAAA"}
@@ -0,0 +1 @@
1
+ import{createLogger}from"../../util/log.mjs";import{appendPathBeforeQuery}from"../../internal/config.mjs";import{trace}from"@opentelemetry/api";const log=createLogger(`replay`),textEncoder=new TextEncoder;async function prepareBody(compressor,rawBytes){if(!compressor.gzip)return{body:rawBytes,encoding:null};try{return{body:await compressor.gzip(rawBytes),encoding:`gzip`}}catch(error){return log.warn(`gzip failed for chunk; sending uncompressed: %o`,error),{body:rawBytes,encoding:null}}}async function deliverChunk({ctx,uploadBaseUrl,authHeaders,compressor,payload,skipCompression}){let sessionId=ctx.getSessionId();if(!sessionId)return;let rawBytes=textEncoder.encode(payload.bodyJson),uncompressedBytes=rawBytes.byteLength,prepared=skipCompression?{body:rawBytes,encoding:null}:await prepareBody(compressor,rawBytes),headers={...authHeaders,"content-type":`application/json`,...prepared.encoding===`gzip`?{"content-encoding":`gzip`}:{}},res=await fetch(appendPathBeforeQuery(uploadBaseUrl,`/v2/replay/upload/${encodeURIComponent(sessionId)}`),{method:`POST`,headers,body:prepared.body});if(!res.ok){log.warn(`replay chunk upload rejected (%d)`,res.status);return}let{objectKey}=await res.json();trace.getTracer(`@interfere/react/replay`).startSpan(`replay.chunk`,{attributes:{"replay.chunk.uri":objectKey,"replay.chunk.size_bytes":uncompressedBytes,...prepared.encoding===`gzip`?{"replay.chunk.compressed_bytes":prepared.body.byteLength}:{},"replay.chunk.event_count":payload.eventCount,...payload.firstTs===null?{}:{"replay.chunk.first_event_ts":payload.firstTs},...payload.lastTs===null?{}:{"replay.chunk.last_event_ts":payload.lastTs}}}).end()}export{deliverChunk};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery.mjs","names":[],"sources":["../../../src/plugins/replay/delivery.ts"],"sourcesContent":["import { trace } from \"@opentelemetry/api\";\n\nimport { appendPathBeforeQuery } from \"../../internal/config.js\";\nimport { createLogger } from \"../../util/log.js\";\nimport type { PluginContext } from \"../lib/types.js\";\nimport type { ReplayChunkPayload } from \"./chunking.js\";\nimport type { Compressor } from \"./compressor-host.js\";\n\nconst log = createLogger(\"replay\");\n\nconst UPLOAD_PATH_PREFIX = \"/v2/replay/upload/\";\nconst TRACER_NAME = \"@interfere/react/replay\";\n\nconst textEncoder = new TextEncoder();\n\ninterface PreparedBody {\n body: Uint8Array<ArrayBuffer>;\n encoding: \"gzip\" | null;\n}\n\nasync function prepareBody(\n compressor: Compressor,\n rawBytes: Uint8Array<ArrayBuffer>\n): Promise<PreparedBody> {\n if (!compressor.gzip) {\n return { body: rawBytes, encoding: null };\n }\n try {\n const compressed = await compressor.gzip(rawBytes);\n return { body: compressed, encoding: \"gzip\" };\n } catch (error) {\n log.warn(\"gzip failed for chunk; sending uncompressed: %o\", error);\n return { body: rawBytes, encoding: null };\n }\n}\n\nexport interface DeliverInput {\n authHeaders: Record<string, string>;\n compressor: Compressor;\n ctx: PluginContext;\n payload: ReplayChunkPayload;\n skipCompression: boolean;\n uploadBaseUrl: string;\n}\n\nexport async function deliverChunk({\n ctx,\n uploadBaseUrl,\n authHeaders,\n compressor,\n payload,\n skipCompression,\n}: DeliverInput): Promise<void> {\n const sessionId = ctx.getSessionId();\n if (!sessionId) {\n return;\n }\n\n const rawBytes = textEncoder.encode(payload.bodyJson);\n const uncompressedBytes = rawBytes.byteLength;\n\n // Worker replies never arrive during unload; the skip path must not await.\n const prepared: PreparedBody = skipCompression\n ? { body: rawBytes, encoding: null }\n : await prepareBody(compressor, rawBytes);\n\n const headers: Record<string, string> = {\n ...authHeaders,\n \"content-type\": \"application/json\",\n ...(prepared.encoding === \"gzip\" ? { \"content-encoding\": \"gzip\" } : {}),\n };\n\n const res = await fetch(\n appendPathBeforeQuery(\n uploadBaseUrl,\n `${UPLOAD_PATH_PREFIX}${encodeURIComponent(sessionId)}`\n ),\n {\n method: \"POST\",\n headers,\n body: prepared.body,\n }\n );\n if (!res.ok) {\n log.warn(\"replay chunk upload rejected (%d)\", res.status);\n return;\n }\n\n const { objectKey } = (await res.json()) as { objectKey: string };\n const span = trace.getTracer(TRACER_NAME).startSpan(\"replay.chunk\", {\n attributes: {\n \"replay.chunk.uri\": objectKey,\n \"replay.chunk.size_bytes\": uncompressedBytes,\n ...(prepared.encoding === \"gzip\"\n ? { \"replay.chunk.compressed_bytes\": prepared.body.byteLength }\n : {}),\n \"replay.chunk.event_count\": payload.eventCount,\n ...(payload.firstTs === null\n ? {}\n : { \"replay.chunk.first_event_ts\": payload.firstTs }),\n ...(payload.lastTs === null\n ? {}\n : { \"replay.chunk.last_event_ts\": payload.lastTs }),\n },\n });\n span.end();\n}\n"],"mappings":"gJAQA,MAAM,IAAM,aAAa,QAAQ,EAK3B,YAAc,IAAI,YAOxB,eAAe,YACb,WACA,SACuB,CACvB,GAAI,CAAC,WAAW,KACd,MAAO,CAAE,KAAM,SAAU,SAAU,IAAK,EAE1C,GAAI,CAEF,MAAO,CAAE,KAAM,MADU,WAAW,KAAK,QAAQ,EACtB,SAAU,MAAO,CAC9C,OAAS,MAAO,CAEd,OADA,IAAI,KAAK,kDAAmD,KAAK,EAC1D,CAAE,KAAM,SAAU,SAAU,IAAK,CAC1C,CACF,CAWA,eAAsB,aAAa,CACjC,IACA,cACA,YACA,WACA,QACA,iBAC8B,CAC9B,IAAM,UAAY,IAAI,aAAa,EACnC,GAAI,CAAC,UACH,OAGF,IAAM,SAAW,YAAY,OAAO,QAAQ,QAAQ,EAC9C,kBAAoB,SAAS,WAG7B,SAAyB,gBAC3B,CAAE,KAAM,SAAU,SAAU,IAAK,EACjC,MAAM,YAAY,WAAY,QAAQ,EAEpC,QAAkC,CACtC,GAAG,YACH,eAAgB,mBAChB,GAAI,SAAS,WAAa,OAAS,CAAE,mBAAoB,MAAO,EAAI,CAAC,CACvE,EAEM,IAAM,MAAM,MAChB,sBACE,cACA,qBAAwB,mBAAmB,SAAS,GACtD,EACA,CACE,OAAQ,OACR,QACA,KAAM,SAAS,IACjB,CACF,EACA,GAAI,CAAC,IAAI,GAAI,CACX,IAAI,KAAK,oCAAqC,IAAI,MAAM,EACxD,MACF,CAEA,GAAM,CAAE,WAAe,MAAM,IAAI,KAAK,EAiBtC,MAhBmB,UAAU,yBAAW,EAAE,UAAU,eAAgB,CAClE,WAAY,CACV,mBAAoB,UACpB,0BAA2B,kBAC3B,GAAI,SAAS,WAAa,OACtB,CAAE,gCAAiC,SAAS,KAAK,UAAW,EAC5D,CAAC,EACL,2BAA4B,QAAQ,WACpC,GAAI,QAAQ,UAAY,KACpB,CAAC,EACD,CAAE,8BAA+B,QAAQ,OAAQ,EACrD,GAAI,QAAQ,SAAW,KACnB,CAAC,EACD,CAAE,6BAA8B,QAAQ,MAAO,CACrD,CACF,CACG,EAAE,IAAI,CACX"}
@@ -0,0 +1,6 @@
1
+ import { Plugin } from "../lib/types.mjs";
2
+
3
+ //#region src/plugins/replay/index.d.ts
4
+ declare const replayPlugin: Plugin;
5
+ //#endregion
6
+ export { replayPlugin as default, replayPlugin };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/plugins/replay/index.ts"],"mappings":";;;cAea,YAAA,EAAc,MAwF1B"}
@@ -0,0 +1 @@
1
+ import{createLogger}from"../../util/log.mjs";import{resolveTargets}from"../../internal/config.mjs";import{onPageHidden,onPageShown}from"../../internal/page-lifecycle.mjs";import{splitReplayEvents,toReplayChunkPayload}from"./chunking.mjs";import{createReplayBuffer}from"./buffer.mjs";import{createCompressor}from"./compressor-host.mjs";import{deliverChunk}from"./delivery.mjs";import{startReplayRecorder}from"./recorder.mjs";const log=createLogger(`replay`),replayPlugin={name:`replay`,setup(ctx){let targets=resolveTargets(),uploadBaseUrl=targets.collectorBaseUrl,authHeaders=Object.fromEntries(targets.ingest.headers.entries()),recorder=null,flushTimer=null,unsubscribeHidden=null,unsubscribeShown=null,isHidden=!1,compressor=createCompressor();function flush(opts){let drained=buffer.drain();if(drained.length===0)return;let{chunks,droppedEventBytes}=splitReplayEvents(drained);for(let bytes of droppedEventBytes)log.warn(`dropping replay event of ~%d bytes: too large to upload`,bytes);for(let group of chunks)deliverChunk({ctx,uploadBaseUrl,authHeaders,compressor,payload:toReplayChunkPayload(group),skipCompression:opts?.skipCompression??!1}).catch(error=>log.warn(`replay chunk dropped: %o`,error))}let buffer=createReplayBuffer({softLimitBytes:4194304,onSoftLimit:flush}),onHide=()=>{isHidden=!0,flush({skipCompression:!0})},onShow=()=>{isHidden&&(isHidden=!1,recorder?.takeFullSnapshot())};return(async()=>{recorder=await startReplayRecorder(event=>{isHidden||buffer.push(event)}),recorder&&(flushTimer=setInterval(flush,1e4),unsubscribeHidden=onPageHidden(onHide),unsubscribeShown=onPageShown(onShow),log.debug(`recording started`))})(),()=>{flush(),recorder?.stop(),flushTimer&&clearInterval(flushTimer),unsubscribeHidden?.(),unsubscribeShown?.(),compressor.dispose()}}};export{replayPlugin as default,replayPlugin};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/plugins/replay/index.ts"],"sourcesContent":["import { resolveTargets } from \"../../internal/config.js\";\nimport { onPageHidden, onPageShown } from \"../../internal/page-lifecycle.js\";\nimport { createLogger } from \"../../util/log.js\";\nimport type { Plugin } from \"../lib/types.js\";\nimport { createReplayBuffer } from \"./buffer.js\";\nimport { splitReplayEvents, toReplayChunkPayload } from \"./chunking.js\";\nimport { createCompressor } from \"./compressor-host.js\";\nimport { deliverChunk } from \"./delivery.js\";\nimport { type ReplayRecorder, startReplayRecorder } from \"./recorder.js\";\n\nconst log = createLogger(\"replay\");\n\nconst FLUSH_INTERVAL_MS = 10_000;\nconst SOFT_FLUSH_BYTES = 4 * 1024 * 1024;\n\nexport const replayPlugin: Plugin = {\n name: \"replay\",\n\n setup(ctx) {\n const targets = resolveTargets();\n const uploadBaseUrl = targets.collectorBaseUrl;\n const authHeaders = Object.fromEntries(targets.ingest.headers.entries());\n\n let recorder: ReplayRecorder | null = null;\n let flushTimer: ReturnType<typeof setInterval> | null = null;\n let unsubscribeHidden: (() => void) | null = null;\n let unsubscribeShown: (() => void) | null = null;\n let isHidden = false;\n const compressor = createCompressor();\n\n function flush(opts?: { skipCompression: boolean }): void {\n const drained = buffer.drain();\n if (drained.length === 0) {\n return;\n }\n const { chunks, droppedEventBytes } = splitReplayEvents(drained);\n for (const bytes of droppedEventBytes) {\n log.warn(\n \"dropping replay event of ~%d bytes: too large to upload\",\n bytes\n );\n }\n for (const group of chunks) {\n deliverChunk({\n ctx,\n uploadBaseUrl,\n authHeaders,\n compressor,\n payload: toReplayChunkPayload(group),\n skipCompression: opts?.skipCompression ?? false,\n }).catch((error: unknown) =>\n log.warn(\"replay chunk dropped: %o\", error)\n );\n }\n }\n\n const buffer = createReplayBuffer({\n softLimitBytes: SOFT_FLUSH_BYTES,\n onSoftLimit: flush,\n });\n\n const onHide = () => {\n isHidden = true;\n flush({ skipCompression: true });\n };\n\n const onShow = () => {\n if (!isHidden) {\n return;\n }\n isHidden = false;\n recorder?.takeFullSnapshot();\n };\n\n const init = async () => {\n recorder = await startReplayRecorder((event) => {\n if (isHidden) {\n return;\n }\n buffer.push(event);\n });\n if (!recorder) {\n return;\n }\n flushTimer = setInterval(flush, FLUSH_INTERVAL_MS);\n unsubscribeHidden = onPageHidden(onHide);\n unsubscribeShown = onPageShown(onShow);\n log.debug(\"recording started\");\n };\n\n init();\n\n return () => {\n flush();\n recorder?.stop();\n if (flushTimer) {\n clearInterval(flushTimer);\n }\n unsubscribeHidden?.();\n unsubscribeShown?.();\n compressor.dispose();\n };\n },\n};\n\nexport default replayPlugin;\n"],"mappings":"waAUA,MAAM,IAAM,aAAa,QAAQ,EAKpB,aAAuB,CAClC,KAAM,SAEN,MAAM,IAAK,CACT,IAAM,QAAU,eAAe,EACzB,cAAgB,QAAQ,iBACxB,YAAc,OAAO,YAAY,QAAQ,OAAO,QAAQ,QAAQ,CAAC,EAEnE,SAAkC,KAClC,WAAoD,KACpD,kBAAyC,KACzC,iBAAwC,KACxC,SAAW,GACT,WAAa,iBAAiB,EAEpC,SAAS,MAAM,KAA2C,CACxD,IAAM,QAAU,OAAO,MAAM,EAC7B,GAAI,QAAQ,SAAW,EACrB,OAEF,GAAM,CAAE,OAAQ,mBAAsB,kBAAkB,OAAO,EAC/D,IAAK,IAAM,SAAS,kBAClB,IAAI,KACF,0DACA,KACF,EAEF,IAAK,IAAM,SAAS,OAClB,aAAa,CACX,IACA,cACA,YACA,WACA,QAAS,qBAAqB,KAAK,EACnC,gBAAiB,MAAM,iBAAmB,EAC5C,CAAC,EAAE,MAAO,OACR,IAAI,KAAK,2BAA4B,KAAK,CAC5C,CAEJ,CAEA,IAAM,OAAS,mBAAmB,CAChC,eAAgB,QAChB,YAAa,KACf,CAAC,EAEK,WAAe,CACnB,SAAW,GACX,MAAM,CAAE,gBAAiB,EAAK,CAAC,CACjC,EAEM,WAAe,CACd,WAGL,SAAW,GACX,UAAU,iBAAiB,EAC7B,EAoBA,OAFA,SAhByB,CACvB,SAAW,MAAM,oBAAqB,OAAU,CAC1C,UAGJ,OAAO,KAAK,KAAK,CACnB,CAAC,EACI,WAGL,WAAa,YAAY,MAAO,GAAiB,EACjD,kBAAoB,aAAa,MAAM,EACvC,iBAAmB,YAAY,MAAM,EACrC,IAAI,MAAM,mBAAmB,EAC/B,GAEK,MAEQ,CACX,MAAM,EACN,UAAU,KAAK,EACX,YACF,cAAc,UAAU,EAE1B,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,QAAQ,CACrB,CACF,CACF"}