@interfere/react 11.0.0-canary.2 → 11.0.0-canary.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/internal/capture-boundary.mjs +1 -1
- package/dist/internal/capture.mjs +1 -1
- package/dist/internal/hydration.d.mts +10 -0
- package/dist/internal/hydration.mjs +1 -0
- package/dist/internal/kernel.d.mts +14 -3
- package/dist/internal/kernel.mjs +1 -1
- package/dist/internal/otel/initial-navigation.d.mts +13 -0
- package/dist/internal/otel/initial-navigation.mjs +1 -0
- package/dist/internal/otel/instrumentations.d.mts +25 -5
- package/dist/internal/otel/instrumentations.mjs +1 -1
- package/dist/internal/react-context.d.mts +1 -0
- package/dist/internal/react-context.mjs +1 -1
- package/dist/internal/server-digest.d.mts +9 -0
- package/dist/internal/server-digest.mjs +1 -0
- package/dist/internal/third-party-hosts.d.mts +14 -0
- package/dist/internal/third-party-hosts.mjs +1 -0
- package/dist/internal/wrapper-singleton.d.mts +1 -0
- package/dist/internal/wrapper-singleton.mjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/plugins/errors.d.mts +1 -1
- package/dist/plugins/errors.mjs +1 -1
- package/dist/plugins/logs.d.mts +1 -1
- package/dist/plugins/logs.mjs +1 -1
- package/dist/plugins/rage-clicks.d.mts +1 -1
- package/dist/plugins/rage-clicks.mjs +1 -1
- package/dist/plugins/replay/index.d.mts +1 -1
- package/dist/plugins/replay/index.mjs +1 -1
- package/dist/tracking/api.d.mts +1 -0
- package/dist/tracking/api.mjs +1 -1
- package/dist/tracking/session.d.mts +1 -0
- package/dist/tracking/session.mjs +1 -1
- package/package.json +25 -31
- package/dist/api.d.mts.map +0 -1
- package/dist/api.mjs.map +0 -1
- package/dist/error-boundary.d.mts.map +0 -1
- package/dist/error-boundary.mjs.map +0 -1
- package/dist/internal/browser-context.d.mts.map +0 -1
- package/dist/internal/browser-context.mjs.map +0 -1
- package/dist/internal/capture-boundary.d.mts.map +0 -1
- package/dist/internal/capture-boundary.mjs.map +0 -1
- package/dist/internal/capture.d.mts.map +0 -1
- package/dist/internal/capture.mjs.map +0 -1
- package/dist/internal/config.d.mts.map +0 -1
- package/dist/internal/config.mjs.map +0 -1
- package/dist/internal/consent.d.mts.map +0 -1
- package/dist/internal/consent.mjs.map +0 -1
- package/dist/internal/console-patch.d.mts.map +0 -1
- package/dist/internal/console-patch.mjs.map +0 -1
- package/dist/internal/dom/actionable.d.mts.map +0 -1
- package/dist/internal/dom/actionable.mjs.map +0 -1
- package/dist/internal/kernel-registry.d.mts.map +0 -1
- package/dist/internal/kernel-registry.mjs.map +0 -1
- package/dist/internal/kernel.d.mts.map +0 -1
- package/dist/internal/kernel.mjs.map +0 -1
- package/dist/internal/otel/exporter.d.mts.map +0 -1
- package/dist/internal/otel/exporter.mjs.map +0 -1
- package/dist/internal/otel/instrumentations.d.mts.map +0 -1
- package/dist/internal/otel/instrumentations.mjs.map +0 -1
- package/dist/internal/otel/page-scope-context-manager.d.mts.map +0 -1
- package/dist/internal/otel/page-scope-context-manager.mjs.map +0 -1
- package/dist/internal/otel/propagation.d.mts.map +0 -1
- package/dist/internal/otel/propagation.mjs.map +0 -1
- package/dist/internal/otel/provider.d.mts.map +0 -1
- package/dist/internal/otel/provider.mjs.map +0 -1
- package/dist/internal/otel/web-vitals.d.mts.map +0 -1
- package/dist/internal/otel/web-vitals.mjs.map +0 -1
- package/dist/internal/page-lifecycle.d.mts.map +0 -1
- package/dist/internal/page-lifecycle.mjs.map +0 -1
- package/dist/internal/plugin-runtime.d.mts.map +0 -1
- package/dist/internal/plugin-runtime.mjs.map +0 -1
- package/dist/internal/react-context.d.mts.map +0 -1
- package/dist/internal/react-context.mjs.map +0 -1
- package/dist/internal/sw.d.mts.map +0 -1
- package/dist/internal/sw.mjs.map +0 -1
- package/dist/internal/version.d.mts.map +0 -1
- package/dist/internal/version.mjs.map +0 -1
- package/dist/internal/wrapper-singleton.d.mts.map +0 -1
- package/dist/internal/wrapper-singleton.mjs.map +0 -1
- package/dist/package.mjs.map +0 -1
- package/dist/plugins/errors.d.mts.map +0 -1
- package/dist/plugins/errors.mjs.map +0 -1
- package/dist/plugins/lib/loader.d.mts.map +0 -1
- package/dist/plugins/lib/loader.mjs.map +0 -1
- package/dist/plugins/lib/types.d.mts.map +0 -1
- package/dist/plugins/logs.d.mts.map +0 -1
- package/dist/plugins/logs.mjs.map +0 -1
- package/dist/plugins/rage-clicks.d.mts.map +0 -1
- package/dist/plugins/rage-clicks.mjs.map +0 -1
- package/dist/plugins/replay/buffer.d.mts.map +0 -1
- package/dist/plugins/replay/buffer.mjs.map +0 -1
- package/dist/plugins/replay/chunking.d.mts.map +0 -1
- package/dist/plugins/replay/chunking.mjs.map +0 -1
- package/dist/plugins/replay/compressor-host.d.mts.map +0 -1
- package/dist/plugins/replay/compressor-host.mjs.map +0 -1
- package/dist/plugins/replay/delivery.d.mts.map +0 -1
- package/dist/plugins/replay/delivery.mjs.map +0 -1
- package/dist/plugins/replay/index.d.mts.map +0 -1
- package/dist/plugins/replay/index.mjs.map +0 -1
- package/dist/plugins/replay/recorder.d.mts.map +0 -1
- package/dist/plugins/replay/recorder.mjs.map +0 -1
- package/dist/provider.d.mts.map +0 -1
- package/dist/provider.mjs.map +0 -1
- package/dist/react-error-handler.d.mts.map +0 -1
- package/dist/react-error-handler.mjs.map +0 -1
- package/dist/tracking/api.d.mts.map +0 -1
- package/dist/tracking/api.mjs.map +0 -1
- package/dist/tracking/device.d.mts.map +0 -1
- package/dist/tracking/device.mjs.map +0 -1
- package/dist/tracking/geo.d.mts.map +0 -1
- package/dist/tracking/geo.mjs.map +0 -1
- package/dist/tracking/session.d.mts.map +0 -1
- package/dist/tracking/session.mjs.map +0 -1
- package/dist/util/bot.d.mts.map +0 -1
- package/dist/util/bot.mjs.map +0 -1
- package/dist/util/global.d.mts.map +0 -1
- package/dist/util/global.mjs.map +0 -1
- package/dist/util/log.d.mts.map +0 -1
- package/dist/util/log.mjs.map +0 -1
- package/dist/util/stringify.d.mts.map +0 -1
- package/dist/util/stringify.mjs.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.mts","names":[],"sources":["../../src/internal/config.ts"],"mappings":";UAUiB,YAAA;EACf,OAAA,EAAS,OAAO;EAChB,GAAA;AAAA;AAAA,UAGe,WAAA;EACf,OAAA,EAAS,OAAO;AAAA;AAAA,iBAwCF,cAAA,CACd,GAAA,UACA,GAAA,UACA,KAAA;AAAA,iBAWc,qBAAA,CAAsB,GAAA,UAAa,IAAY;AAAA,iBAO/C,cAAA;EA9DC;;;;AACC;EAmEhB,gBAAA;EACA,MAAA,EAAQ,YAAA;;;;;;EAMR,MAAA,EAAQ,WAAA;EACR,OAAA,EAAS,YAAA;AAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.mjs","names":[],"sources":["../../src/internal/config.ts"],"sourcesContent":["import { API_PATHS, API_URL } from \"@interfere/constants/api\";\nimport { resolveRoutePrefix } from \"@interfere/constants/route-prefix\";\n\nimport { getGlobal } from \"../util/global.js\";\n\nconst PUBLIC_KEY_QUERY = \"pk\";\nconst PROXY_MODE_GLOBAL = \"__INTERFERE_PROXY_MODE__\";\nconst TRAILING_SLASH_RE = /\\/$/;\nconst RELATIVE_URL_BASE = \"https://interfere.local\";\n\nexport interface IngestTarget {\n headers: Headers;\n url: string;\n}\n\nexport interface AuthHeaders {\n headers: Headers;\n}\n\nfunction resolvePublicKey(): string | undefined {\n const injected = getGlobal<string>(\"__INTERFERE_PUBLIC_KEY__\");\n if (injected) {\n return injected;\n }\n\n if (typeof process !== \"undefined\") {\n return process.env[\"INTERFERE_PUBLIC_KEY\"] ?? undefined;\n }\n\n return;\n}\n\nfunction resolveApiUrlOverride(): string | undefined {\n const injected = getGlobal<string>(\"__INTERFERE_API_URL__\");\n if (injected) {\n return injected;\n }\n\n return;\n}\n\nfunction isForceEnableActive(): boolean {\n return !!getGlobal<boolean>(\"__INTERFERE_FORCE_ENABLE__\");\n}\n\nfunction isProxyModeActive(): boolean {\n return !!getGlobal<boolean>(PROXY_MODE_GLOBAL);\n}\n\nfunction serializeUrl(input: string, url: URL): string {\n if (input.startsWith(\"/\")) {\n return `${url.pathname}${url.search}`;\n }\n return url.toString();\n}\n\nexport function withQueryParam(\n url: string,\n key: string,\n value: string\n): string {\n const next = new URL(url, RELATIVE_URL_BASE);\n next.searchParams.set(key, value);\n return serializeUrl(url, next);\n}\n\nfunction appendPublicKey(url: string, publicKey: string | undefined): string {\n return publicKey ? withQueryParam(url, PUBLIC_KEY_QUERY, publicKey) : url;\n}\n\nexport function appendPathBeforeQuery(url: string, path: string): string {\n const next = new URL(url, RELATIVE_URL_BASE);\n const suffix = path.startsWith(\"/\") ? path : `/${path}`;\n next.pathname = `${next.pathname.replace(TRAILING_SLASH_RE, \"\")}${suffix}`;\n return serializeUrl(url, next);\n}\n\nexport function resolveTargets(): {\n /**\n * Base URL for OTLP exports — the trace/metric exporters append the\n * sink path themselves. Avoids the kernel having to regex-strip\n * `/v2/sink` off the configured URL to derive a base.\n */\n collectorBaseUrl: string;\n config: IngestTarget;\n /**\n * Identity headers shared across every collector-bound request\n * (OTLP exporters, replay upload, session sync). One source of\n * truth for auth + force-enable.\n */\n ingest: AuthHeaders;\n session: IngestTarget;\n} {\n const publicKey = resolvePublicKey();\n const headers = new Headers({ \"content-type\": \"application/json\" });\n\n if (isForceEnableActive()) {\n headers.set(\"x-interfere-force-enable\", \"1\");\n }\n\n const baseUrl =\n publicKey && !isProxyModeActive()\n ? (resolveApiUrlOverride() ?? API_URL)\n : resolveRoutePrefix();\n const authenticatedBaseUrl = appendPublicKey(baseUrl, publicKey);\n\n return {\n collectorBaseUrl: authenticatedBaseUrl,\n config: {\n url: appendPublicKey(`${baseUrl}${API_PATHS.CONFIG}`, publicKey),\n headers,\n },\n ingest: { headers },\n session: {\n url: appendPublicKey(`${baseUrl}${API_PATHS.SESSION}`, publicKey),\n headers,\n },\n };\n}\n"],"mappings":"oKAKA,MAEM,kBAAoB,MACpB,kBAAoB,0BAW1B,SAAS,kBAAuC,CAC9C,IAAM,SAAW,UAAkB,0BAA0B,EAC7D,GAAI,SACF,OAAO,SAGT,GAAI,OAAO,QAAY,IACrB,OAAO,QAAQ,IAAI,sBAA2B,IAAA,EAIlD,CAEA,SAAS,uBAA4C,CACnD,IAAM,SAAW,UAAkB,uBAAuB,EAC1D,GAAI,SACF,OAAO,QAIX,CAEA,SAAS,qBAA+B,CACtC,MAAO,CAAC,CAAC,UAAmB,4BAA4B,CAC1D,CAEA,SAAS,mBAA6B,CACpC,MAAO,CAAC,CAAC,UAAmB,0BAAiB,CAC/C,CAEA,SAAS,aAAa,MAAe,IAAkB,CAIrD,OAHI,MAAM,WAAW,GAAG,EACf,GAAG,IAAI,WAAW,IAAI,SAExB,IAAI,SAAS,CACtB,CAEA,SAAgB,eACd,IACA,IACA,MACQ,CACR,IAAM,KAAO,IAAI,IAAI,IAAK,iBAAiB,EAE3C,OADA,KAAK,aAAa,IAAI,IAAK,KAAK,EACzB,aAAa,IAAK,IAAI,CAC/B,CAEA,SAAS,gBAAgB,IAAa,UAAuC,CAC3E,OAAO,UAAY,eAAe,IAAK,KAAkB,SAAS,EAAI,GACxE,CAEA,SAAgB,sBAAsB,IAAa,KAAsB,CACvE,IAAM,KAAO,IAAI,IAAI,IAAK,iBAAiB,EACrC,OAAS,KAAK,WAAW,GAAG,EAAI,KAAO,IAAI,OAEjD,MADA,MAAK,SAAW,GAAG,KAAK,SAAS,QAAQ,kBAAmB,EAAE,IAAI,SAC3D,aAAa,IAAK,IAAI,CAC/B,CAEA,SAAgB,gBAed,CACA,IAAM,UAAY,iBAAiB,EAC7B,QAAU,IAAI,QAAQ,CAAE,eAAgB,kBAAmB,CAAC,EAE9D,oBAAoB,GACtB,QAAQ,IAAI,2BAA4B,GAAG,EAG7C,IAAM,QACJ,WAAa,CAAC,kBAAkB,EAC3B,sBAAsB,GAAK,QAC5B,mBAAmB,EAGzB,MAAO,CACL,iBAH2B,gBAAgB,QAAS,SAGf,EACrC,OAAQ,CACN,IAAK,gBAAgB,GAAG,UAAU,UAAU,SAAU,SAAS,EAC/D,OACF,EACA,OAAQ,CAAE,OAAQ,EAClB,QAAS,CACP,IAAK,gBAAgB,GAAG,UAAU,UAAU,UAAW,SAAS,EAChE,OACF,CACF,CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"consent.d.mts","names":[],"sources":["../../src/internal/consent.ts"],"mappings":";;;;cASa,eAAA;EAAA,SAGoB,SAAA;EAAA,SAAA,MAAA;AAAA;AAAA,iBAcjB,wBAAA,CAAyB,GAAA,EAAK,SAAA,GAAY,eAAe;AAAA,iBAIzD,gBAAA,CACd,QAAA,EAAU,eAAA,EACV,YAAA,EAAc,YAAY;AAAA,iBASZ,kBAAA,CACd,IAAA,EAAM,SAAA,EACN,YAAA,EAAc,YAAY;AAAA,iBAKZ,qBAAA,CAAsB,OAAA,GAAU,YAAA,GAAe,YAAY;AAAA,iBAI3D,iBAAA,CACd,OAAA,EAAS,YAAA,SACT,IAAA,EAAM,YAAY"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"consent.mjs","names":[],"sources":["../../src/internal/consent.ts"],"sourcesContent":["import type { EventType } from \"@interfere/types/sdk/envelope\";\nimport {\n type ConsentCategory,\n type ConsentState,\n type GateableCategory,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\n\nexport const DEFAULT_CONSENT = {\n analytics: true,\n replay: true,\n} as const satisfies ConsentState;\n\nconst PLUGIN_CONSENT_BY_KEY = Object.fromEntries(\n PLUGIN_MANIFEST.map((plugin) => [plugin.name, plugin.consentCategory])\n) as Record<PluginKey, ConsentCategory>;\n\nconst EVENT_CONSENT_BY_TYPE = Object.fromEntries(\n PLUGIN_MANIFEST.flatMap((plugin) =>\n plugin.events.map((event) => [event.name, plugin.consentCategory] as const)\n )\n) as Record<EventType, ConsentCategory>;\n\nconst GATEABLE_CATEGORIES = Object.keys(DEFAULT_CONSENT) as GateableCategory[];\n\nexport function getPluginConsentCategory(key: PluginKey): ConsentCategory {\n return PLUGIN_CONSENT_BY_KEY[key];\n}\n\nexport function isConsentAllowed(\n category: ConsentCategory,\n consentState: ConsentState | null\n): boolean {\n return (\n category === \"necessary\" ||\n consentState === null ||\n consentState[category] === true\n );\n}\n\nexport function shouldCaptureEvent(\n type: EventType,\n consentState: ConsentState | null\n): boolean {\n return isConsentAllowed(EVENT_CONSENT_BY_TYPE[type], consentState);\n}\n\nexport function resolveGrantedConsent(consent?: ConsentState): ConsentState {\n return consent ?? { ...DEFAULT_CONSENT };\n}\n\nexport function hasConsentChanged(\n current: ConsentState | null,\n next: ConsentState | null\n): boolean {\n for (const category of GATEABLE_CATEGORIES) {\n if (current?.[category] !== next?.[category]) {\n return true;\n }\n }\n return false;\n}\n"],"mappings":"mEASA,MAAa,gBAAkB,CAC7B,UAAW,GACX,OAAQ,EACV,EAEM,sBAAwB,OAAO,YACnC,gBAAgB,IAAK,QAAW,CAAC,OAAO,KAAM,OAAO,eAAe,CAAC,CACvE,EAEM,sBAAwB,OAAO,YACnC,gBAAgB,QAAS,QACvB,OAAO,OAAO,IAAK,OAAU,CAAC,MAAM,KAAM,OAAO,eAAe,CAAU,CAC5E,CACF,EAEM,oBAAsB,OAAO,KAAK,eAAe,EAEvD,SAAgB,yBAAyB,IAAiC,CACxE,OAAO,sBAAsB,IAC/B,CAEA,SAAgB,iBACd,SACA,aACS,CACT,OACE,WAAa,aACb,eAAiB,MACjB,aAAa,YAAc,EAE/B,CAEA,SAAgB,mBACd,KACA,aACS,CACT,OAAO,iBAAiB,sBAAsB,MAAO,YAAY,CACnE,CAEA,SAAgB,sBAAsB,QAAsC,CAC1E,OAAO,SAAW,CAAE,GAAG,eAAgB,CACzC,CAEA,SAAgB,kBACd,QACA,KACS,CACT,IAAK,IAAM,YAAY,oBACrB,GAAI,UAAU,YAAc,OAAO,UACjC,MAAO,GAGX,MAAO,EACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"console-patch.d.mts","names":[],"sources":["../../src/internal/console-patch.ts"],"mappings":";;AAUA;;;;AAMU;AAEV;;;;cARa,cAAA;AAAA,KAQD,YAAA,WAAuB,cAAc;AAAA,KAErC,cAAA,IAAkB,KAAA,EAAO,YAAY,EAAE,IAAA;AAAA,iBAiDnC,aAAA,CAAc,OAAuB,EAAd,cAAc;;iBAYrC,0BAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"console-patch.mjs","names":[],"sources":["../../src/internal/console-patch.ts"],"sourcesContent":["/**\n * Single point of `console.{debug,log,info,warn,error}` patching. The\n * errors plugin and the logs plugin both want to observe console calls;\n * before this consolidation, each patched independently and the\n * resulting wrap-order coupling made teardown leaky.\n *\n * Now: a single set of patched methods is installed on first\n * subscription, fans out to every subscriber, and is uninstalled on the\n * last unsubscription. Handlers never wrap each other; they observe.\n */\nexport const CONSOLE_LEVELS = [\n \"debug\",\n \"log\",\n \"info\",\n \"warn\",\n \"error\",\n] as const;\n\nexport type ConsoleLevel = (typeof CONSOLE_LEVELS)[number];\n\nexport type ConsoleHandler = (level: ConsoleLevel, args: unknown[]) => void;\n\nconst handlers = new Set<ConsoleHandler>();\nconst originals: Partial<Record<ConsoleLevel, (...args: unknown[]) => void>> =\n {};\nlet installed = false;\n\nfunction install(): void {\n if (installed) {\n return;\n }\n installed = true;\n const target = globalThis.console as unknown as Record<\n ConsoleLevel,\n (...args: unknown[]) => void\n >;\n for (const level of CONSOLE_LEVELS) {\n const orig = target[level];\n if (typeof orig !== \"function\") {\n continue;\n }\n originals[level] = orig;\n target[level] = (...args: unknown[]) => {\n orig.apply(globalThis.console, args);\n for (const handler of handlers) {\n handler(level, args);\n }\n };\n }\n}\n\nfunction uninstall(): void {\n if (!installed) {\n return;\n }\n installed = false;\n const target = globalThis.console as unknown as Record<\n ConsoleLevel,\n (...args: unknown[]) => void\n >;\n for (const level of CONSOLE_LEVELS) {\n const orig = originals[level];\n if (orig) {\n target[level] = orig;\n delete originals[level];\n }\n }\n}\n\nexport function onConsoleCall(handler: ConsoleHandler): () => void {\n install();\n handlers.add(handler);\n return () => {\n handlers.delete(handler);\n if (handlers.size === 0) {\n uninstall();\n }\n };\n}\n\n/** Test hook — drops every handler and restores originals. */\nexport function _resetConsolePatchForTests(): void {\n handlers.clear();\n uninstall();\n}\n"],"mappings":"AAUA,MAAa,eAAiB,CAC5B,QACA,MACA,OACA,OACA,OACF,EAMM,SAAW,IAAI,IACf,UACJ,CAAC,EACH,IAAI,UAAY,GAEhB,SAAS,SAAgB,CACvB,GAAI,UACF,OAEF,UAAY,GACZ,IAAM,OAAS,WAAW,QAI1B,IAAK,IAAM,SAAS,eAAgB,CAClC,IAAM,KAAO,OAAO,OAChB,OAAO,MAAS,aAGpB,UAAU,OAAS,KACnB,OAAO,QAAU,GAAG,OAAoB,CACtC,KAAK,MAAM,WAAW,QAAS,IAAI,EACnC,IAAK,IAAM,WAAW,SACpB,QAAQ,MAAO,IAAI,CAEvB,EACF,CACF,CAEA,SAAS,WAAkB,CACzB,GAAI,CAAC,UACH,OAEF,UAAY,GACZ,IAAM,OAAS,WAAW,QAI1B,IAAK,IAAM,SAAS,eAAgB,CAClC,IAAM,KAAO,UAAU,OACnB,OACF,OAAO,OAAS,KAChB,OAAO,UAAU,OAErB,CACF,CAEA,SAAgB,cAAc,QAAqC,CAGjE,OAFA,QAAQ,EACR,SAAS,IAAI,OAAO,MACP,CACX,SAAS,OAAO,OAAO,EACnB,SAAS,OAAS,GACpB,UAAU,CAEd,CACF,CAGA,SAAgB,4BAAmC,CACjD,SAAS,MAAM,EACf,UAAU,CACZ"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"actionable.d.mts","names":[],"sources":["../../../src/internal/dom/actionable.ts"],"mappings":";;AAkCA;;;;AAAwC;AAWxC;;;;;iBAXgB,YAAA,CAAa,EAAW,EAAP,OAAO;AAAA,iBAWxB,iBAAA,CAAkB,MAAA,EAAQ,WAAA,UAAqB,OAAO;AAAA,UAcrD,oBAAA;EACf,SAAA;EACA,IAAA;EACA,EAAA;EACA,IAAA;EACA,IAAA;EACA,GAAA;EACA,IAAA;EAAA,CACC,GAAA;AAAA;AAAA,iBAGa,kBAAA,CAAmB,EAAA,EAAI,OAAA,GAAU,oBAAoB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"actionable.mjs","names":[],"sources":["../../../src/internal/dom/actionable.ts"],"sourcesContent":["/**\n * Single source of truth for \"what counts as a clickable element\" across\n * the SDK. Used by both the `pages` plugin (which captures `ui_event`\n * envelopes) and the OTel `UserInteractionInstrumentation` enrichment\n * hook (which gates span creation + stamps target attrs). Before this\n * was extracted, the two sites diverged and produced inconsistent\n * notions of \"actionable.\"\n *\n * `data-track-click` is the customer escape hatch — any element opted\n * in via that attribute is treated as actionable regardless of tag/role.\n */\n\nconst ACTIONABLE_TAGS = new Set([\n \"a\",\n \"button\",\n \"input\",\n \"select\",\n \"textarea\",\n \"summary\",\n \"label\",\n]);\n\nconst ACTIONABLE_ROLES = new Set([\n \"button\",\n \"link\",\n \"menuitem\",\n \"tab\",\n \"checkbox\",\n \"radio\",\n \"switch\",\n]);\n\nconst TEXT_TRUNCATE = 120;\n\nexport function isActionable(el: Element): boolean {\n if (ACTIONABLE_TAGS.has(el.tagName.toLowerCase())) {\n return true;\n }\n const role = el.getAttribute(\"role\");\n if (role && ACTIONABLE_ROLES.has(role)) {\n return true;\n }\n return el.hasAttribute(\"data-track-click\");\n}\n\nexport function closestActionable(target: EventTarget | null): Element | null {\n if (!(target instanceof Element)) {\n return null;\n }\n let el: Element | null = target;\n while (el) {\n if (isActionable(el)) {\n return el;\n }\n el = el.parentElement;\n }\n return null;\n}\n\nexport interface ActionableDescriptor {\n ariaLabel?: string | undefined;\n href?: string | undefined;\n id?: string | undefined;\n name?: string | undefined;\n role?: string | undefined;\n tag: string;\n text?: string | undefined;\n [key: string]: unknown;\n}\n\nexport function describeActionable(el: Element): ActionableDescriptor {\n const desc: ActionableDescriptor = {\n tag: el.tagName.toLowerCase(),\n };\n if (el.id) {\n desc.id = el.id;\n }\n const role = el.getAttribute(\"role\");\n if (role) {\n desc.role = role;\n }\n const name = el.getAttribute(\"name\");\n if (name) {\n desc.name = name;\n }\n const ariaLabel = el.getAttribute(\"aria-label\");\n if (ariaLabel) {\n desc.ariaLabel = ariaLabel;\n }\n if (el instanceof HTMLAnchorElement) {\n desc.href = el.href;\n }\n const text = el.textContent?.trim().slice(0, TEXT_TRUNCATE);\n if (text) {\n desc.text = text;\n }\n return desc;\n}\n"],"mappings":"AAYA,MAAM,gBAAkB,IAAI,IAAI,CAC9B,IACA,SACA,QACA,SACA,WACA,UACA,OACF,CAAC,EAEK,iBAAmB,IAAI,IAAI,CAC/B,SACA,OACA,WACA,MACA,WACA,QACA,QACF,CAAC,EAID,SAAgB,aAAa,GAAsB,CACjD,GAAI,gBAAgB,IAAI,GAAG,QAAQ,YAAY,CAAC,EAC9C,MAAO,GAET,IAAM,KAAO,GAAG,aAAa,MAAM,EAInC,OAHI,MAAQ,iBAAiB,IAAI,IAAI,EAC5B,GAEF,GAAG,aAAa,kBAAkB,CAC3C,CAEA,SAAgB,kBAAkB,OAA4C,CAC5E,GAAI,EAAE,kBAAkB,SACtB,OAAO,KAET,IAAI,GAAqB,OACzB,KAAO,IAAI,CACT,GAAI,aAAa,EAAE,EACjB,OAAO,GAET,GAAK,GAAG,aACV,CACA,OAAO,IACT,CAaA,SAAgB,mBAAmB,GAAmC,CACpE,IAAM,KAA6B,CACjC,IAAK,GAAG,QAAQ,YAAY,CAC9B,EACI,GAAG,KACL,KAAK,GAAK,GAAG,IAEf,IAAM,KAAO,GAAG,aAAa,MAAM,EAC/B,OACF,KAAK,KAAO,MAEd,IAAM,KAAO,GAAG,aAAa,MAAM,EAC/B,OACF,KAAK,KAAO,MAEd,IAAM,UAAY,GAAG,aAAa,YAAY,EAC1C,YACF,KAAK,UAAY,WAEf,cAAc,oBAChB,KAAK,KAAO,GAAG,MAEjB,IAAM,KAAO,GAAG,aAAa,KAAK,EAAE,MAAM,EAAG,GAAa,EAI1D,OAHI,OACF,KAAK,KAAO,MAEP,IACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kernel-registry.d.mts","names":[],"sources":["../../src/internal/kernel-registry.ts"],"mappings":";;;iBAsBgB,cAAA,CAAe,MAAc,EAAN,MAAM;AAAA,iBAS7B,gBAAA,CAAiB,MAAc,EAAN,MAAM;AAAA,iBAM/B,YAAA,IAAgB,MAAM"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kernel-registry.mjs","names":[],"sources":["../../src/internal/kernel-registry.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\nimport type { Kernel } from \"./kernel.js\";\n\nconst log = createLogger(\"kernel-registry\");\n\n/**\n * Module-scoped active kernel reference. The framework wrapper\n * (`@interfere/next`, `@interfere/vite`) registers its kernel here on\n * `init()` so the public `api.ts` helpers can find it without each customer\n * call site threading the kernel through.\n *\n * Single-kernel by design: each app bundle has its own module instance, so\n * the common case (one app, one init) gets a clean global. The warn on\n * overwrite catches HMR / accidental double-init / module-federated MFEs\n * that share `@interfere/react` across surfaces — those setups need to\n * reach for `kernel.recordException()` directly rather than the registry.\n *\n * @internal Wrappers only. Importing this from app code will silently\n * de-fang the public api helpers.\n */\nlet active: Kernel | null = null;\n\nexport function registerKernel(kernel: Kernel): void {\n if (active && active !== kernel) {\n log.warn(\n \"init() called twice with different kernels; later one wins. The public capture()/span() helpers route only to the latest kernel — multi-surface apps should call kernel.recordException() directly.\"\n );\n }\n active = kernel;\n}\n\nexport function unregisterKernel(kernel: Kernel): void {\n if (active === kernel) {\n active = null;\n }\n}\n\nexport function activeKernel(): Kernel | null {\n return active;\n}\n"],"mappings":"0CAGA,MAAM,IAAM,aAAa,iBAAiB,EAiB1C,IAAI,OAAwB,KAE5B,SAAgB,eAAe,OAAsB,CAC/C,QAAU,SAAW,QACvB,IAAI,KACF,qMACF,EAEF,OAAS,MACX,CAEA,SAAgB,iBAAiB,OAAsB,CACjD,SAAW,SACb,OAAS,KAEb,CAEA,SAAgB,cAA8B,CAC5C,OAAO,MACT"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kernel.d.mts","names":[],"sources":["../../src/internal/kernel.ts"],"mappings":";;;;;;;;;;;;;;;;;;iBAsDgB,aAAA,CAAc,eAA0B;AAAA,UA+CvC,aAAA;EACf,OAAA,GAAU,YAAA;EAhDI;;;;AAAwC;AA+CxD;;EASE,OAAA;EARU;EAUV,KAAA,UAAe,UAAA,CAAW,KAAA;EAC1B,OAAA,GAAU,eAAA;EAgBU;;;;;;;EARpB,WAAA;EAT0B;;;;;;;EAiB1B,OAAA,aAAoB,cAAA;AAAA;;;;;;;UASL,cAAA;EAOf;;;;;;EAAA,qBAAA,GAAwB,2BAAA;EAsBR;;;AAMP;AAUX;;EA/BE,UAAA,aAAuB,MAAA;EAuCkB;;;;;;EAhCzC,oBAAA,aAAiC,MAAA;EAgCjC;;;;;;;EAxBA,YAAA,IAAgB,QAAA;EAuCA;AAGlB;;;;EApCE,SAAA;AAAA;;;;;AAsCwB;AAG1B;;UA/BiB,qBAAA,SAA8B,aAAA;EAgCtC;;;;;;;EAxBP,sCAAA,GAAyC,kBAAA;EAyB7B;;;;AAAwB;AAGtC;;EApBE,gCAAA,GAAmC,YAAA;EAqBnC;AACS;AAGX;;EApBE,iCAAA,GAAoC,aAAA;EAqBlB;EAnBlB,gBAAA;AAAA;AAAA,UAGe,aAAA;EACf,GAAA,IAAO,YAAA;EACP,GAAA,CAAI,KAAA,GAAQ,YAAY;AAAA;AAAA,UAGT,cAAA;EACf,GAAA,IAAO,cAAA;EACP,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA;AAAA;AAAA,UAGd,YAAA;EACf,WAAA;EACA,SAAS;AAAA;AAAA,UAGM,aAAA;EACf,KAAA,IAAS,SAAS;EAClB,WAAA;AAAA;AAAA,UAGe,mBAAA;EAuBK;;AAAc;AAcpC;;;EAdsB,SAhBX,YAAA,YAAwB,aAAA;EAgCd;;;;;;;EAAA,SAxBV,WAAA;EAsKM;;;;;;EAAA,SA/JN,cAAA,YAA0B,aAAA;EAAA,SAC1B,SAAA,EAAW,cAAA;AAAA;;;;;;;;;;;;cAcT,MAAA,YAAkB,aAAA;EAAA,SACpB,OAAA,EAAS,aAAA;EAAA,SACT,QAAA,EAAU,cAAA;EAAA,SACV,MAAA,EAAQ,YAAA;EAAA,SACR,OAAA,EAAS,aAAA;EAAA,iBAED,OAAA;EAAA,iBAEA,IAAA;EAAA,QAET,OAAA;EAAA,QACA,IAAA;EAAA,QACA,WAAA;EAAA,QACA,qBAAA;cAEI,OAAA,EAAS,cAAA;EA2EZ;EAvCT,aAAA,CAAc,OAAA,EAAS,aAAA;EAAA,QAIf,cAAA;EAoCA;EA1BR,YAAA;EA2EA;;;;;;;;;;;;;;;;;;;AAuDwB;EA1GxB,eAAA,CACE,KAAA,EAAO,KAAA,GAAQ,iBAAA,EACf,IAAA,EAAM,mBAAA;EAyHiB;;;AACG;AAS9B;;;EAlFE,SAAA,CAAU,KAAA;IACR,YAAA;IACA,cAAA;IACA,IAAA;IACA,UAAA,GAAa,MAAA;EAAA;EA+Ef;;;;AACe;AAsNjB;;EAlRQ,KAAA,IAAS,OAAA;EAkRqB;AAAA;AAyBtC;;;;AAAyD;EA9RvD,UAAA,CAAW,MAAA,EAAQ,kBAAA,EAAoB,OAAA;;;;;EASvC,WAAA,CAAY,KAAA,EAAO,YAAA;EASb,OAAA,IAAW,OAAA;AAAA;AAAA,UAiBT,iBAAA;EACR,IAAA,GAAO,qBAAqB;AAAA;;;;;;;iBASR,YAAA,CACpB,KAAA,GAAO,iBAAA,GACN,OAAA,CAAQ,MAAA;AAAA,iBAsNK,sBAAA;;;;;;;;;;;;iBAyBA,0BAAA,IAA8B,WAAW"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kernel.mjs","names":[],"sources":["../../src/internal/kernel.ts"],"sourcesContent":["import type { IngestedFrame } from \"@interfere/types/data/frame\";\nimport type { SessionId } from \"@interfere/types/data/session\";\nimport {\n type ReleaseSlug,\n releaseSlugSchema,\n} from \"@interfere/types/releases/slug\";\nimport {\n isNonErrorException,\n type NonErrorException,\n shouldDropBrowserExtensionNoise,\n shouldDropUnresolvableStack,\n toExceptions,\n} from \"@interfere/types/sdk/errors\";\nimport type { IdentifyParams } from \"@interfere/types/sdk/identify\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\nimport type { ErrorMechanism } from \"@interfere/types/sdk/plugins/payload/errors\";\nimport type { RemoteConfig } from \"@interfere/types/sdk/remote-config\";\n\nimport { type Attributes, trace } from \"@opentelemetry/api\";\nimport type { LogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport type { MetricReader } from \"@opentelemetry/sdk-metrics\";\nimport type { SpanProcessor } from \"@opentelemetry/sdk-trace-base\";\n\nimport type { PluginOverrides } from \"../plugins/lib/loader.js\";\nimport type { PluginContext } from \"../plugins/lib/types.js\";\nimport { SessionTracker } from \"../tracking/api.js\";\nimport { getGlobal } from \"../util/global.js\";\nimport { createLogger } from \"../util/log.js\";\nimport { safeStringify } from \"../util/stringify.js\";\nimport type { AuthHeaders, IngestTarget } from \"./config.js\";\nimport { resolveTargets } from \"./config.js\";\nimport type {\n CaptureFailedRequestsOption,\n OtelProviderHandle,\n} from \"./otel/index.js\";\n\nexport type {\n CaptureFailedRequestsOption,\n CaptureFailedRequestsOptions,\n HttpStatusCodeRange,\n} from \"./otel/index.js\";\n\nimport { onPageHidden } from \"./page-lifecycle.js\";\nimport { PluginRuntime } from \"./plugin-runtime.js\";\nimport { registerServiceWorker, type SwMessage } from \"./sw.js\";\nimport { PRODUCER_VERSION } from \"./version.js\";\n\nconst log = createLogger(\"kernel\");\n\nconst TRAILING_SLASH_RE = /\\/$/;\n\nconst EXCEPTION_TRACER_NAME = \"@interfere/react/exception\";\nconst EXCEPTION_SPAN_NAME = \"interfere.exception.capture\";\n\nexport function buildSdkStack(wrapperVersions?: string[]): string[] {\n return [...(wrapperVersions ?? []), PRODUCER_VERSION];\n}\n\nfunction buildExceptionEventAttrs(\n value: Error | NonErrorException,\n opts: RecordExceptionOpts\n): Attributes {\n const attrs: Attributes = {\n \"interfere.exception.mechanism\": opts.mechanism.type,\n \"interfere.exception.handled\": opts.mechanism.handled,\n };\n if (opts.errorDigest) {\n attrs[\"interfere.error.digest\"] = opts.errorDigest;\n }\n if (isNonErrorException(value)) {\n attrs[\"exception.type\"] = value.type;\n attrs[\"exception.message\"] = value.value;\n attrs[\"interfere.exception.kind\"] = \"non-error\";\n\n attrs[\"interfere.exception.serialized\"] = safeStringify(value.serialized);\n return attrs;\n }\n attrs[\"exception.type\"] = value.name;\n attrs[\"exception.message\"] = value.message;\n attrs[\"interfere.exception.kind\"] = \"error\";\n if (value.stack) {\n attrs[\"exception.stacktrace\"] = value.stack;\n }\n\n attrs[\"interfere.exception.chain\"] = safeStringify(\n toExceptions(value, opts.mechanism)\n );\n\n if (opts.appendFrames && opts.appendFrames.length > 0) {\n attrs[\"interfere.exception.appended_frames\"] = safeStringify(\n opts.appendFrames\n );\n }\n if (opts.fallbackFrames && opts.fallbackFrames.length > 0) {\n attrs[\"interfere.exception.fallback_frames\"] = safeStringify(\n opts.fallbackFrames\n );\n }\n return attrs;\n}\n\nexport interface KernelOptions {\n consent?: ConsentState;\n /**\n * Override the automatic dev-mode guard. When `undefined`, the SDK\n * auto-detects: it disables itself if `process.env[\"NODE_ENV\"]` is not\n * `\"production\"` (Node / webpack / Next.js). In environments where\n * `process` does not exist (Vite, CRA, plain browser) the SDK\n * defaults to **enabled** — pass `false` to disable explicitly.\n */\n enabled?: boolean;\n /** Override `globalThis.fetch` for tests / non-browser hosts. */\n fetch?: typeof globalThis.fetch;\n plugins?: PluginOverrides;\n /**\n * Override the OTel `service.name` resource attribute. Defaults to\n * `\"interfere-sdk\"`. Customers running multiple frontends in one\n * monitoring backend (e.g. `\"@interfere/website\"` vs\n * `\"@interfere/app\"`) set this so spans/metrics/logs slice\n * cleanly by surface.\n */\n serviceName?: string;\n /**\n * `false` skips the OTel module entirely — the bundler code-splits\n * `internal/otel/*` out of this path so the error-only bundle never\n * imports the OTel SDK. `true` (default) loads it with default\n * instrumentation config. An object loads it with the supplied\n * config.\n */\n tracing?: boolean | TracingOptions;\n}\n\n/**\n * Per-instrumentation tuning. Every field is optional; defaults match\n * what `interfere/homepage` and `interfere/dashboard` use today via\n * the internal `observability/browser/rum.ts`. New customers don't\n * need to touch any of these — sensible defaults ship out of the box.\n */\nexport interface TracingOptions {\n /**\n * Promote failed `fetch` / `XHR` responses to exception events on the\n * active span. Off by default — when on, defaults to capturing 5xx.\n * Only fires for same-origin and `propagateContextUrls` matches.\n * See {@link CaptureFailedRequestsOption}.\n */\n captureFailedRequests?: CaptureFailedRequestsOption;\n /**\n * Additional URL patterns to skip when creating fetch / XHR spans.\n * The SDK's own collector endpoint is always ignored; customer-side\n * third-party SDKs (Clerk, Sentry, GTM, etc.) are also skipped by\n * default — supplement with anything specific to your app.\n */\n ignoreUrls?: (string | RegExp)[];\n /**\n * URL patterns that should receive W3C `traceparent` + `baggage`\n * headers on outgoing fetch / XHR. Same-origin requests always\n * propagate; this allowlist enables cross-origin propagation\n * (separate API origin, internal service mesh, etc.).\n */\n propagateContextUrls?: (string | RegExp)[];\n /**\n * Resolve a URL pathname to a low-cardinality route template\n * (e.g. `/blog/[slug]`). Without this, dynamic routes produce one\n * unique span / metric label per visited slug — a cardinality\n * hazard. Used by fetch, document-load resource enrichment,\n * user-interaction, long-task, and web-vitals.\n */\n resolveRoute?: (pathname: string) => string | undefined;\n /**\n * `false` disables Core Web Vitals capture (LCP/FCP/TTFB/INP/CLS).\n * Defaults to enabled; customers who run their own vitals reporting\n * can opt out.\n */\n webVitals?: boolean;\n}\n\n/**\n * @internal\n * Framework wrappers (`@interfere/next`, `@interfere/vite`) pass extra\n * fields through `createKernel` that customers never set themselves.\n * Kept off the customer-facing `KernelOptions` to make the public surface\n * clean.\n */\nexport interface KernelInternalOptions extends KernelOptions {\n /**\n * Extra log-record processors fanned into the LoggerProvider's\n * processor list. Used by `@interfere/observability` for internal-only\n * dual-write to BetterStack from `interfere/homepage` +\n * `interfere/dashboard`. **Not part of the customer surface** — the\n * SDK doesn't expose a way to fan-out to other observability vendors.\n */\n _internalAdditionalLogRecordProcessors?: LogRecordProcessor[];\n /**\n * Extra metric readers fanned into the MeterProvider's reader list.\n * See `_internalAdditionalLogRecordProcessors`. Used to keep web\n * vitals histograms landing on the BetterStack-fronting OTel\n * collector after the rum.ts → SDK migration moved the primary path\n * to `/v2/sink` (Tinybird-only).\n */\n _internalAdditionalMetricReaders?: MetricReader[];\n /**\n * Extra span processors fanned into the WebTracerProvider's processor\n * list. See `_internalAdditionalLogRecordProcessors`.\n */\n _internalAdditionalSpanProcessors?: SpanProcessor[];\n /** Wrapper SDK versions (e.g. `@interfere/next@10.0.0`). */\n _wrapperVersions?: string[];\n}\n\nexport interface KernelConsent {\n get(): ConsentState | null;\n set(value?: ConsentState): void;\n}\n\nexport interface KernelIdentity {\n get(): IdentifyParams | null;\n set(params: IdentifyParams): Promise<void>;\n}\n\nexport interface KernelDevice {\n getDeviceId(): string | null;\n getFpHash(): string | null;\n}\n\nexport interface KernelSession {\n getId(): SessionId | null;\n getWindowId(): string | null;\n}\n\nexport interface RecordExceptionOpts {\n /**\n * Frames always appended to the first exception's stack. Used by\n * `<ErrorBoundary>` to inject React's component-stack frames as\n * supplementary context — these add on top of the real JS frames, they\n * don't replace them.\n */\n readonly appendFrames?: readonly IngestedFrame[];\n /**\n * `error.digest` from React when an RSC server-side throw is\n * sanitised and rebuilt as a synthetic Error on the client. The only\n * identifier that pairs the redacted client capture with the\n * unredacted server capture — enrichment mixes this into the\n * fingerprint so both events group as one problem.\n */\n readonly errorDigest?: string;\n /**\n * Frames adopted only when the first exception's parsed stack is empty.\n * Used by the `window.onerror` handler when the browser hands us\n * source/line/col but the `Error` itself has a degenerate stack — never\n * glued on top of a real stack.\n */\n readonly fallbackFrames?: readonly IngestedFrame[];\n readonly mechanism: ErrorMechanism;\n}\n\n/**\n * The replacement for the old `Client` god-object. Pure construction graph\n * via `createKernel`; no module-level mutable state, no global lookups.\n * Framework wrappers (`@interfere/next`, `@interfere/vite`) own the\n * singleton lifecycle.\n *\n * Implements `PluginContext` directly: plugins are wired with the kernel\n * itself rather than a closure adapter. The kernel⇄runtime cycle is\n * resolved by constructing the kernel first and binding the runtime via\n * `attachRuntime` immediately after.\n */\nexport class Kernel implements PluginContext {\n readonly consent: KernelConsent;\n readonly identity: KernelIdentity;\n readonly device: KernelDevice;\n readonly session: KernelSession;\n\n private readonly tracker: SessionTracker;\n\n private readonly seen = new WeakSet<Error>();\n\n private runtime: PluginRuntime | null = null;\n private otel: OtelProviderHandle | null = null;\n private otelDispose: (() => void) | null = null;\n private unsubscribePageHidden: (() => void) | null = null;\n\n constructor(tracker: SessionTracker) {\n this.tracker = tracker;\n\n this.consent = {\n get: () => this.requireRuntime().getConsent(),\n set: (value) => {\n const runtime = this.requireRuntime();\n if (value) {\n runtime.setConsent(value);\n } else {\n runtime.resetConsent();\n }\n },\n };\n\n this.identity = {\n get: () => this.tracker.getIdentity(),\n set: (params) => this.tracker.identify(params),\n };\n\n this.device = {\n getDeviceId: () => this.tracker.getDeviceId(),\n getFpHash: () => this.tracker.getFpHash(),\n };\n\n this.session = {\n getId: () => this.tracker.sessionId(),\n getWindowId: () => this.tracker.windowId(),\n };\n\n this.unsubscribePageHidden = onPageHidden(() => {\n this.flush().catch(() => undefined);\n });\n }\n\n /** @internal Bound by `createKernel` immediately after construction. */\n attachRuntime(runtime: PluginRuntime): void {\n this.runtime = runtime;\n }\n\n private requireRuntime(): PluginRuntime {\n if (!this.runtime) {\n throw new Error(\n \"Kernel runtime not attached. createKernel must call attachRuntime before any consent/dispose path runs.\"\n );\n }\n return this.runtime;\n }\n\n /** PluginContext shim: plugins expect a non-null string. */\n getSessionId(): string {\n return this.tracker.sessionId() ?? \"\";\n }\n\n /**\n * Records an exception event on the active OTel span. No envelope\n * path — the SDK is OTel-only. Single dedup boundary: drops repeats\n * of the same `Error` instance, plus browser-extension noise and\n * unresolvable stacks. Upstream callers (the errors plugin's\n * `window.onerror` / `console.error` / `unhandledrejection`, the\n * React error boundary) just call this — they don't pre-filter.\n *\n * Does NOT mark the active span as failed. A console.error inside a\n * customer-owned `span()` block would otherwise taint the customer's\n * span even when the customer caught the error explicitly. The\n * customer's `span()` helper sets status itself when it observes a\n * throw.\n *\n * `appendFrames` are always appended (component-stack supplements);\n * `fallbackFrames` are only adopted when the parsed stack is empty\n * (`window.onerror` source/line/col when the Error has no usable\n * stack). Both ride on the serialised `interfere.exception.chain`\n * attribute when the chain is non-trivial.\n */\n recordException(\n value: Error | NonErrorException,\n opts: RecordExceptionOpts\n ): void {\n if (!isNonErrorException(value)) {\n if (this.seen.has(value)) {\n return;\n }\n this.seen.add(value);\n }\n\n if (!isNonErrorException(value)) {\n const stacks: string[] = [];\n let current: Error | undefined = value;\n for (let depth = 0; current && depth < 5; depth += 1) {\n if (current.stack) {\n stacks.push(current.stack);\n }\n current = current.cause instanceof Error ? current.cause : undefined;\n }\n if (\n shouldDropBrowserExtensionNoise(stacks) ||\n shouldDropUnresolvableStack(stacks)\n ) {\n return;\n }\n }\n\n const attrs = buildExceptionEventAttrs(value, opts);\n const active = trace.getActiveSpan();\n if (active?.isRecording()) {\n active.addEvent(\"exception\", attrs);\n return;\n }\n const span = this.otel?.tracerProvider\n .getTracer(EXCEPTION_TRACER_NAME)\n .startSpan(EXCEPTION_SPAN_NAME);\n if (!span) {\n return;\n }\n span.addEvent(\"exception\", attrs);\n span.end();\n }\n\n /**\n * Emits an OTel `LogRecord` via the kernel's logger provider. Used by\n * `plugins/logs.ts` to capture string-only `console.*` calls (the\n * errors plugin still owns Error-bearing console calls — class\n * boundary). No-ops when OTel is not wired (`tracing: false`) since\n * there's no logger provider to emit through.\n */\n recordLog(input: {\n severityText: string;\n severityNumber: number;\n body: string;\n attributes?: Record<string, string>;\n }): void {\n if (!this.otel) {\n return;\n }\n this.otel.loggerProvider.getLogger(\"@interfere/react\").emit({\n severityText: input.severityText,\n severityNumber: input.severityNumber,\n body: input.body,\n ...(input.attributes ? { attributes: input.attributes } : {}),\n });\n }\n\n /**\n * Force-flushes the OTel exporters. Returns once they've settled\n * (success or failure) so callers driving unload — page-lifecycle\n * handlers, integration test teardown — can actually await\n * completion. The OTel SDK swallows export errors internally; we\n * surface the resolution either way.\n */\n async flush(): Promise<void> {\n if (this.otel) {\n await this.otel.flush();\n }\n }\n\n /**\n * @internal Wired by `createKernel` after the OTel module has been\n * lazy-loaded. Held on the kernel so `flush()` and `dispose()` can fan\n * out to the providers. The tracer/meter providers stay private —\n * customers don't get raw OTel access; the public surface is the\n * `span()` / `capture()` helpers in `api.ts`.\n */\n attachOtel(handle: OtelProviderHandle, dispose: () => void): void {\n this.otel = handle;\n this.otelDispose = dispose;\n }\n\n /**\n * Provider-driven consent sync. Pass `undefined` to clear consent (treats\n * the prop as \"unmanaged\"); pass a state to apply it.\n */\n syncConsent(value: ConsentState | undefined): void {\n const runtime = this.requireRuntime();\n if (value) {\n runtime.setConsent(value);\n return;\n }\n runtime.resetConsent();\n }\n\n async dispose(): Promise<void> {\n this.unsubscribePageHidden?.();\n this.unsubscribePageHidden = null;\n if (this.runtime) {\n await this.runtime.dispose();\n }\n this.tracker.dispose();\n\n this.otelDispose?.();\n this.otelDispose = null;\n if (this.otel) {\n await this.otel.shutdown();\n this.otel = null;\n }\n }\n}\n\ninterface CreateKernelInput {\n opts?: KernelInternalOptions;\n}\n\n/**\n * Pure construction graph. Awaits the remote-config fetch before resolving,\n * so the runtime sees a fully-applied config before any capture is attempted\n * by the caller. Removes the capture-before-config race the old `Client`\n * had.\n */\nexport async function createKernel(\n input: CreateKernelInput = {}\n): Promise<Kernel> {\n const { opts = {} } = input;\n const fetcher = opts.fetch ?? globalThis.fetch.bind(globalThis);\n const targets = resolveTargets();\n\n log.info(\"collector: %s\", targets.collectorBaseUrl);\n\n if (getGlobal<boolean>(\"__INTERFERE_FORCE_ENABLE__\")) {\n log.warn(\n \"FORCE_ENABLE is active. Events will be dropped by production collectors. This should only be set during local development.\"\n );\n }\n\n const tracker = new SessionTracker({ target: targets.session, fetcher });\n\n if (opts._wrapperVersions?.length) {\n (globalThis as Record<string, unknown>)[\"__INTERFERE_SDK_STACK__\"] =\n opts._wrapperVersions;\n }\n\n const kernel = new Kernel(tracker);\n const runtime = new PluginRuntime(kernel, opts.plugins, opts.consent);\n kernel.attachRuntime(runtime);\n\n tracker.start();\n runtime.start();\n\n if (opts.tracing !== false) {\n const tracingOpts =\n typeof opts.tracing === \"object\" ? opts.tracing : undefined;\n await wireOtel(kernel, targets, opts._wrapperVersions, {\n ...(tracingOpts ?? {}),\n ...(opts.serviceName ? { serviceName: opts.serviceName } : {}),\n ...(opts._internalAdditionalSpanProcessors\n ? {\n additionalSpanProcessors: opts._internalAdditionalSpanProcessors,\n }\n : {}),\n ...(opts._internalAdditionalLogRecordProcessors\n ? {\n additionalLogRecordProcessors:\n opts._internalAdditionalLogRecordProcessors,\n }\n : {}),\n ...(opts._internalAdditionalMetricReaders\n ? {\n additionalMetricReaders: opts._internalAdditionalMetricReaders,\n }\n : {}),\n });\n }\n\n await fetchRemoteConfig(targets.config, fetcher, runtime);\n\n registerServiceWorker({\n onMessage: (msg) => recordSwMessage(kernel, msg),\n }).catch(() => undefined);\n\n return kernel;\n}\n\nfunction recordSwMessage(\n kernel: { recordLog: Kernel[\"recordLog\"] },\n msg: SwMessage\n): void {\n kernel.recordLog({\n severityText: \"info\",\n severityNumber: 9,\n body: msg.type,\n attributes: {\n \"url.full\": msg.url,\n ...(msg.reason ? { \"interfere.sw.reason\": msg.reason } : {}),\n },\n });\n}\n\n/**\n * Lazy-imports the OTel module so `init({ tracing: false })` keeps the\n * error-only bundle path light — the dynamic `import()` is what tells the\n * bundler to code-split. Inherits the kernel's resolved auth headers so\n * OTLP exports use the same collector URL and headers the rest of the SDK\n * resolved; no separate auth knob.\n *\n * Module-load failures are NOT caught — a bundled dynamic import only\n * fails on OOM or module-federation split-brain, both real problems we\n * want to surface, not silently flip into \"tracing disabled.\"\n */\ninterface ResolvedTracingOptions extends TracingOptions {\n additionalLogRecordProcessors?: LogRecordProcessor[];\n additionalMetricReaders?: MetricReader[];\n additionalSpanProcessors?: SpanProcessor[];\n serviceName?: string;\n}\n\nasync function wireOtel(\n kernel: Kernel,\n targets: {\n collectorBaseUrl: string;\n config: IngestTarget;\n ingest: AuthHeaders;\n session: IngestTarget;\n },\n wrapperVersions: string[] | undefined,\n tracing: ResolvedTracingOptions\n): Promise<void> {\n const {\n buildOtelProvider,\n captureWebVitals,\n readPropagationFromDocument,\n registerBundledInstrumentations,\n } = await import(\"./otel/index.js\");\n\n const collectorUrl = targets.collectorBaseUrl.replace(TRAILING_SLASH_RE, \"\");\n const releaseSlug = readReleaseSlugFromGlobals();\n\n const handle = buildOtelProvider({\n collectorUrl,\n authHeaders: targets.ingest.headers,\n sdkStack: buildSdkStack(wrapperVersions),\n\n getSessionId: () => kernel.session.getId(),\n\n deviceId: kernel.device.getDeviceId(),\n releaseSlug,\n ...(tracing.serviceName ? { serviceName: tracing.serviceName } : {}),\n ...(tracing.additionalSpanProcessors\n ? { additionalSpanProcessors: tracing.additionalSpanProcessors }\n : {}),\n ...(tracing.additionalLogRecordProcessors\n ? {\n additionalLogRecordProcessors: tracing.additionalLogRecordProcessors,\n }\n : {}),\n ...(tracing.additionalMetricReaders\n ? { additionalMetricReaders: tracing.additionalMetricReaders }\n : {}),\n });\n\n const extracted = readPropagationFromDocument();\n if (extracted) {\n handle.contextManager.setPageScope(extracted);\n }\n\n const dispose = registerBundledInstrumentations({\n tracerProvider: handle.tracerProvider,\n loggerProvider: handle.loggerProvider,\n // Skip auto-instrumenting every fetch the SDK itself makes.\n // Without this we trace OTLP sink POSTs (infinite loop) and the\n // SDK's own config / session-sync requests — and worse, those\n // self-fetches' transient failures (DOMException timeouts on\n // flaky networks, ECONNRESET on the homepage's outbound\n // collector calls) get recorded as `exception` events and end up\n // in `events_pending`, burning enrichment cycles on\n // pipeline-internal noise that has nothing to do with the\n // customer's app. Measured 2026-05-12: 100% of `events_pending`\n // exceptions were children of `fetch GET …/v2/config` / `…/v2/sink`\n // spans.\n //\n // Exact-string ignore on the resolved target URLs (set at SDK\n // boot from `resolveTargets`) so customer proxies on\n // `/api/interfere/v2/*` are caught too. The OTLP sink lives at\n // `${collectorBaseUrl}/v2/sink` (`COLLECTOR_SINK_PATH` in the\n // exporter); config and session land on full URLs already.\n\n ignoreUrls: [\n `${collectorUrl}/v2/sink`,\n targets.config.url,\n targets.session.url,\n ],\n ...(tracing.propagateContextUrls\n ? { propagateContextUrls: tracing.propagateContextUrls }\n : {}),\n ...(tracing.resolveRoute ? { resolveRoute: tracing.resolveRoute } : {}),\n ...(tracing.captureFailedRequests === undefined\n ? {}\n : { captureFailedRequests: tracing.captureFailedRequests }),\n });\n\n if (tracing.webVitals !== false) {\n captureWebVitals({\n meter: handle.meterProvider.getMeter(\"@interfere/react/web-vitals\"),\n flush: () => handle.flush(),\n ...(tracing.resolveRoute ? { resolveRoute: tracing.resolveRoute } : {}),\n });\n }\n\n kernel.attachOtel(handle, dispose);\n}\n\nasync function fetchRemoteConfig(\n configTarget: IngestTarget,\n fetcher: typeof globalThis.fetch,\n runtime: PluginRuntime\n): Promise<void> {\n try {\n const headers = Object.fromEntries(configTarget.headers.entries());\n const res = await fetcher(configTarget.url, {\n method: \"GET\",\n headers,\n signal: AbortSignal.timeout(10_000),\n });\n if (!res.ok) {\n return;\n }\n const config = (await res.json()) as RemoteConfig;\n if (config?.plugins) {\n runtime.applyRemoteConfig(config.plugins);\n log.debug(\"applied remote config\");\n }\n } catch {\n log.warn(\"remote config fetch failed, using local defaults\");\n }\n}\n\nexport function isEnabledByEnvironment(): boolean {\n if (typeof process === \"undefined\" || !process.env) {\n return true;\n }\n const nodeEnv = process.env[\"NODE_ENV\"];\n if (nodeEnv === \"production\" || nodeEnv === undefined) {\n return true;\n }\n return !!(\n getGlobal<boolean>(\"__INTERFERE_FORCE_ENABLE__\") ||\n process.env[\"INTERFERE_FORCE_ENABLE\"]\n );\n}\n\n/**\n * Build-time-derived release slug. The build step\n * (`@interfere/next/withInterfere`, `@interfere/vite/plugin`) takes the\n * leading 16 hex chars of the commit SHA (`rel_<16hex>`) and stamps it into\n * the bundle as `__INTERFERE_RELEASE_SLUG__`. The collector derives the same\n * value from the create-release request, so the bundle's runtime slug is\n * guaranteed to match the row without a round-trip.\n *\n * Returns `null` when no slug was injected (dev builds, customers who\n * haven't wired up the plugin).\n */\nexport function readReleaseSlugFromGlobals(): ReleaseSlug | null {\n const value = getGlobal<unknown>(\"__INTERFERE_RELEASE_SLUG__\");\n\n return typeof value === \"string\" ? releaseSlugSchema.parse(value) : null;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"exporter.d.mts","names":[],"sources":["../../../src/internal/otel/exporter.ts"],"mappings":";;;;;;UAgDU,cAAA;EAAA;;;;EAKR,WAAA,EAAa,OAAO;EAAP;EAEb,YAAA;AAAA;AAAY;AAAA,iBAIE,YAAA,CAAa,YAAoB;;;;AAAA;AAUjD;;iBAAgB,cAAA,CAAe,KAAqB,EAAd,cAAc;;AAAA;AAMnD;;;;;;;;;;;;;;;;;cAwFK,mBAAA,YAA+B,YAAA;EAAA,iBAClB,GAAA;cAEL,GAAA;EAIZ,MAAA,CACE,KAAA,EAAO,YAAA,IACP,cAAA,GAAiB,MAAA,EAAQ,YAAA;EAc3B,QAAA,IAAY,OAAA;EAIZ,UAAA,IAAc,OAAA;AAAA;;AAAO;AAAA;;cASjB,iBAAA;EAAA,iBACa,GAAA;cAEL,GAAA;EAIZ,MAAA,CACE,IAAA,EAAM,iBAAA,IACN,cAAA,GAAiB,MAAA,EAAQ,YAAA;EAc3B,QAAA,IAAY,OAAA;EAIZ,UAAA,IAAc,OAAA;AAAA;;;;;;;;;;;;cAgBV,oBAAA,YAAgC,kBAAA;EAAA,iBACnB,GAAA;cAEL,GAAA;EAIZ,MAAA,CACE,OAAA,EAAS,eAAA,EACT,cAAA,GAAiB,MAAA,EAAQ,YAAA;EAU3B,4BAAA,IAAgC,sBAAA;EAIhC,UAAA,IAAc,OAAA;EAId,QAAA,IAAY,OAAA;AAAA;AAAA,iBAKE,yBAAA,CACd,KAAA,EAAO,cAAA,GACN,mBAAmB;AAAA,iBAIN,0BAAA,CACd,KAAA,EAAO,cAAA,GACN,oBAAoB;AAAA,iBAIP,uBAAA,CACd,KAAA,EAAO,cAAA,GACN,iBAAiB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"exporter.mjs","names":[],"sources":["../../../src/internal/otel/exporter.ts"],"sourcesContent":["import { diag } from \"@opentelemetry/api\";\nimport type { ExportResult } from \"@opentelemetry/core\";\nimport { ExportResultCode } from \"@opentelemetry/core\";\nimport { JsonLogsSerializer } from \"@opentelemetry/otlp-transformer/build/esm/logs/json/logs.js\";\nimport { JsonMetricsSerializer } from \"@opentelemetry/otlp-transformer/build/esm/metrics/json/metrics.js\";\nimport { JsonTraceSerializer } from \"@opentelemetry/otlp-transformer/build/esm/trace/json/trace.js\";\nimport type { ReadableLogRecord } from \"@opentelemetry/sdk-logs\";\nimport type {\n PushMetricExporter,\n ResourceMetrics,\n} from \"@opentelemetry/sdk-metrics\";\nimport { AggregationTemporality } from \"@opentelemetry/sdk-metrics\";\nimport type { ReadableSpan, SpanExporter } from \"@opentelemetry/sdk-trace-base\";\n\nimport { appendPathBeforeQuery, withQueryParam } from \"../config.js\";\nimport { PRODUCER_VERSION } from \"../version.js\";\n\n/**\n * Opaque collector ingest path. Single endpoint for every OTLP signal —\n * server-side dispatches by which `resource*` block the request carries.\n *\n * Semantically nondescript so common adblock filter lists (EasyPrivacy,\n * uBlock) don't fingerprint it the way they do `traces` / `metrics` /\n * `logs` / `telemetry`. Customer-domain proxy mode is still the\n * recommended path for browser SDKs; this is the second line of defence\n * for pk-authenticated browser clients.\n */\nconst COLLECTOR_SINK_PATH = \"/v2/sink\";\n\n/**\n * Identity gate values the collector accepts via URL query when their\n * matching headers can't be set.\n *\n * `navigator.sendBeacon` (the only browser transport that survives\n * `visibilitychange→hidden`) only attaches `Content-Type` via the\n * `Blob` — no other request headers. We rely on that exclusively for\n * the browser SDK's primary path, so the identity gates the collector\n * enforces have to ride the URL instead.\n *\n * Public-key query exposure note: public keys ship in browser bundles\n * and are visible in DevTools by design, so URL exposure is no worse\n * than the existing header path. API keys deliberately have **no**\n * query fallback — see `surfaceAkAuth` for the matching server-side\n * rationale.\n */\nconst PRODUCER_VERSION_QUERY = \"_pv\";\n\nconst JSON_CONTENT_TYPE = \"application/json\";\ninterface ExporterCommon {\n /**\n * Kept for wrapper compatibility. Public-key identity is baked into\n * `collectorUrl` as a `pk` query parameter before exporters are built.\n */\n authHeaders: Headers;\n /** Base collector URL — the opaque sink path is appended. */\n collectorUrl: string;\n}\n\n/** Exported for direct unit testing. */\nexport function buildSinkUrl(collectorUrl: string): string {\n return appendPathBeforeQuery(collectorUrl, COLLECTOR_SINK_PATH);\n}\n\n/**\n * Beacon-flavoured sink URL — encodes the producer-version (always)\n * while preserving the `pk` query already baked into `collectorUrl`.\n *\n * Exported for direct unit testing.\n */\nexport function buildBeaconUrl(input: ExporterCommon): string {\n return withQueryParam(\n buildSinkUrl(input.collectorUrl),\n PRODUCER_VERSION_QUERY,\n PRODUCER_VERSION\n );\n}\n\n/**\n * Returns true iff the runtime can dispatch a `navigator.sendBeacon`\n * call. Browser-only; server contexts (SSR, prerender, Vitest's `node`\n * environment) trip the typeof guards.\n */\nfunction canSendBeacon(): boolean {\n return (\n typeof navigator !== \"undefined\" &&\n typeof navigator.sendBeacon === \"function\"\n );\n}\n\n/**\n * Dispatches an OTLP payload via `navigator.sendBeacon` and always\n * reports `SUCCESS` to the BSP. `sendBeacon` is fire-and-forget by\n * spec — a `true` return only means \"the user agent accepted the\n * payload into its delivery queue\", not \"the server received it\".\n *\n * Why never report `FAILED`:\n * - `BatchSpanProcessor._flushOneBatch` rejects its promise on\n * `code !== SUCCESS`, and that rejection propagates up through\n * `forceFlush()` / `shutdown()`. In production, that surfaces\n * during Vercel function teardown (and other awaited unload\n * paths) as an unhandled rejection. In tests, it surfaces as\n * `provider.test.tsx > passes through useSession` — vitest's\n * `afterEach(close)` awaits `kernel.dispose() → BSP.shutdown\n * → _flushAll`, the rejection bubbles, and the test fails.\n * - `sendBeacon`'s `false` return signals a *local* drop (per-call\n * ~64KiB ceiling, per-page queue full, disallowed scheme). No\n * network request was attempted, so the service-worker backstop\n * in `internal/sw.ts` can't intercept it either. Retrying inside\n * the exporter would just hit the same browser limit again.\n *\n * The data is gone, but the export *completed* in the only sense\n * that matters to the BSP. A `diag.debug` keeps visibility for any\n * OTel diag listener wired into the kernel.\n */\nfunction reportBeacon(\n url: string,\n payload: Uint8Array | undefined,\n signal: \"trace\" | \"log\" | \"metric\",\n resultCallback: (result: ExportResult) => void\n): void {\n if (!payload || payload.byteLength === 0) {\n diag.debug(`[interfere/beacon] ${signal} serializer returned no bytes`);\n resultCallback({ code: ExportResultCode.SUCCESS });\n return;\n }\n if (!canSendBeacon()) {\n diag.debug(\n `[interfere/beacon] navigator.sendBeacon unavailable; dropping ${signal} batch (${payload.byteLength}B)`\n );\n resultCallback({ code: ExportResultCode.SUCCESS });\n return;\n }\n\n const blob = new Blob([payload as Uint8Array<ArrayBuffer>], {\n type: JSON_CONTENT_TYPE,\n });\n if (!navigator.sendBeacon(url, blob)) {\n diag.debug(\n `[interfere/beacon] navigator.sendBeacon refused ${signal} payload (oversized or queue full); dropping ${payload.byteLength}B`\n );\n }\n resultCallback({ code: ExportResultCode.SUCCESS });\n}\n\n/**\n * Browser-side OTLP trace exporter.\n *\n * Dispatches every export via `navigator.sendBeacon` — which is the\n * only browser transport that reliably commits a request the page is\n * also tearing down (`visibilitychange→hidden`, hard navigation). The\n * `keepalive: true` fetch path the OTLP HTTP exporter ships with\n * works for small payloads but falls back to ordinary fetch (which\n * the renderer aborts on unload) once the cumulative 64KiB / 9-\n * concurrent budget is exhausted. Production data on 2026-05-11\n * attributed ~15% browser-fetch span loss to that fallback; the\n * beacon path closes that hole by design.\n *\n * Identity rides the URL because beacons can't carry custom headers.\n *\n * No retry on failure. The service worker backstop captures 5xx /\n * network failures separately by intercepting the same beacon POST\n * and queueing into IndexedDB for replay (`internal/sw.ts`).\n */\nclass BeaconTraceExporter implements SpanExporter {\n private readonly url: string;\n\n constructor(url: string) {\n this.url = url;\n }\n\n export(\n spans: ReadableSpan[],\n resultCallback: (result: ExportResult) => void\n ): void {\n if (spans.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS });\n return;\n }\n reportBeacon(\n this.url,\n JsonTraceSerializer.serializeRequest(spans),\n \"trace\",\n resultCallback\n );\n }\n\n shutdown(): Promise<void> {\n return Promise.resolve();\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n}\n\n/**\n * Browser-side OTLP log exporter — same beacon transport, log\n * payload. See `BeaconTraceExporter` for the design rationale.\n */\nclass BeaconLogExporter {\n private readonly url: string;\n\n constructor(url: string) {\n this.url = url;\n }\n\n export(\n logs: ReadableLogRecord[],\n resultCallback: (result: ExportResult) => void\n ): void {\n if (logs.length === 0) {\n resultCallback({ code: ExportResultCode.SUCCESS });\n return;\n }\n reportBeacon(\n this.url,\n JsonLogsSerializer.serializeRequest(logs),\n \"log\",\n resultCallback\n );\n }\n\n shutdown(): Promise<void> {\n return Promise.resolve();\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n}\n\n/**\n * Browser-side OTLP metric exporter — beacon transport, metric\n * payload, `DELTA` temporality.\n *\n * `DELTA` matches what the OTLP HTTP exporter the kit's previous\n * `OTLPMetricExporter` was configured with (see the\n * `temporalityPreference: AggregationTemporalityPreference.DELTA`\n * arg `provider.ts` used to pass). Returning the same temporality\n * for every instrument type keeps the wire shape downstream\n * consumers receive unchanged across the migration.\n */\nclass BeaconMetricExporter implements PushMetricExporter {\n private readonly url: string;\n\n constructor(url: string) {\n this.url = url;\n }\n\n export(\n metrics: ResourceMetrics,\n resultCallback: (result: ExportResult) => void\n ): void {\n reportBeacon(\n this.url,\n JsonMetricsSerializer.serializeRequest(metrics),\n \"metric\",\n resultCallback\n );\n }\n\n selectAggregationTemporality(): AggregationTemporality {\n return AggregationTemporality.DELTA;\n }\n\n forceFlush(): Promise<void> {\n return Promise.resolve();\n }\n\n shutdown(): Promise<void> {\n return Promise.resolve();\n }\n}\n\nexport function createBeaconTraceExporter(\n input: ExporterCommon\n): BeaconTraceExporter {\n return new BeaconTraceExporter(buildBeaconUrl(input));\n}\n\nexport function createBeaconMetricExporter(\n input: ExporterCommon\n): BeaconMetricExporter {\n return new BeaconMetricExporter(buildBeaconUrl(input));\n}\n\nexport function createBeaconLogExporter(\n input: ExporterCommon\n): BeaconLogExporter {\n return new BeaconLogExporter(buildBeaconUrl(input));\n}\n"],"mappings":"miBA2DA,SAAgB,aAAa,aAA8B,CACzD,OAAO,sBAAsB,aAAc,UAAmB,CAChE,CAQA,SAAgB,eAAe,MAA+B,CAC5D,OAAO,eACL,aAAa,MAAM,YAAY,EAC/B,MACA,gBACF,CACF,CAOA,SAAS,eAAyB,CAChC,OACE,OAAO,UAAc,KACrB,OAAO,UAAU,YAAe,UAEpC,CA2BA,SAAS,aACP,IACA,QACA,OACA,eACM,CACN,GAAI,CAAC,SAAW,QAAQ,aAAe,EAAG,CACxC,KAAK,MAAM,sBAAsB,OAAO,8BAA8B,EACtE,eAAe,CAAE,KAAM,iBAAiB,OAAQ,CAAC,EACjD,MACF,CACA,GAAI,CAAC,cAAc,EAAG,CACpB,KAAK,MACH,iEAAiE,OAAO,UAAU,QAAQ,WAAW,GACvG,EACA,eAAe,CAAE,KAAM,iBAAiB,OAAQ,CAAC,EACjD,MACF,CAEA,IAAM,KAAO,IAAI,KAAK,CAAC,OAAkC,EAAG,CAC1D,KAAM,kBACR,CAAC,EACI,UAAU,WAAW,IAAK,IAAI,GACjC,KAAK,MACH,mDAAmD,OAAO,+CAA+C,QAAQ,WAAW,EAC9H,EAEF,eAAe,CAAE,KAAM,iBAAiB,OAAQ,CAAC,CACnD,CAqBA,IAAM,oBAAN,KAAkD,CAChD,IAEA,YAAY,IAAa,CACvB,KAAK,IAAM,GACb,CAEA,OACE,MACA,eACM,CACN,GAAI,MAAM,SAAW,EAAG,CACtB,eAAe,CAAE,KAAM,iBAAiB,OAAQ,CAAC,EACjD,MACF,CACA,aACE,KAAK,IACL,oBAAoB,iBAAiB,KAAK,EAC1C,QACA,cACF,CACF,CAEA,UAA0B,CACxB,OAAO,QAAQ,QAAQ,CACzB,CAEA,YAA4B,CAC1B,OAAO,QAAQ,QAAQ,CACzB,CACF,EAMM,kBAAN,KAAwB,CACtB,IAEA,YAAY,IAAa,CACvB,KAAK,IAAM,GACb,CAEA,OACE,KACA,eACM,CACN,GAAI,KAAK,SAAW,EAAG,CACrB,eAAe,CAAE,KAAM,iBAAiB,OAAQ,CAAC,EACjD,MACF,CACA,aACE,KAAK,IACL,mBAAmB,iBAAiB,IAAI,EACxC,MACA,cACF,CACF,CAEA,UAA0B,CACxB,OAAO,QAAQ,QAAQ,CACzB,CAEA,YAA4B,CAC1B,OAAO,QAAQ,QAAQ,CACzB,CACF,EAaM,qBAAN,KAAyD,CACvD,IAEA,YAAY,IAAa,CACvB,KAAK,IAAM,GACb,CAEA,OACE,QACA,eACM,CACN,aACE,KAAK,IACL,sBAAsB,iBAAiB,OAAO,EAC9C,SACA,cACF,CACF,CAEA,8BAAuD,CACrD,OAAO,uBAAuB,KAChC,CAEA,YAA4B,CAC1B,OAAO,QAAQ,QAAQ,CACzB,CAEA,UAA0B,CACxB,OAAO,QAAQ,QAAQ,CACzB,CACF,EAEA,SAAgB,0BACd,MACqB,CACrB,OAAO,IAAI,oBAAoB,eAAe,KAAK,CAAC,CACtD,CAEA,SAAgB,2BACd,MACsB,CACtB,OAAO,IAAI,qBAAqB,eAAe,KAAK,CAAC,CACvD,CAEA,SAAgB,wBACd,MACmB,CACnB,OAAO,IAAI,kBAAkB,eAAe,KAAK,CAAC,CACpD"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentations.d.mts","names":[],"sources":["../../../src/internal/otel/instrumentations.ts"],"mappings":";;;;;UAyBU,eAAA;EACR,OAAA;EACA,MAAM;AAAA;AAAA,KAGH,gBAAA,GAAmB,OAAA,GAAU,WAAA,GAAc,WAAA,GAAc,GAAA;;AAHtD;AAAA;;KASI,mBAAA;AAAA,UAEK,4BAAA;EARiB;EAUhC,WAAA,YAAuB,mBAAmB;AAAA;;KAIhC,2BAAA,aAER,4BAA4B;AAAA,iBA4EvB,mBAAA,CACP,IAAA,EAAM,IAAA,EACN,OAAA,EAAS,gBAAA,EACT,MAAA,EAAQ,QAAA,GAAW,eAAA,EACnB,aAAA,WAAwB,mBAAA,WACxB,YAAA,GAAe,GAAA,sBACf,YAAA,IAAgB,QAAA;AAAA,cAuDL,4BAAA,SAA4B,mBAAsB;AAAA,UA2F9C,qBAAA;EApP6C;EAsP5D,qBAAA,GAAwB,2BAAA;EAtPuC;AAMjE;;;;AAA+B;EAuP7B,UAAA,YAAsB,MAAA;EACtB,cAAA,GAAiB,cAAA;;;AApPyB;AAI5C;;EAsPE,oBAAA,aAAiC,MAAA;EApPH;AAAA;AAAC;;;EA0P/B,YAAA,IAAgB,QAAA;EA5KP;;;;EAiLT,cAAA,EAAgB,iBAAA;AAAA;;;;;;;;;;;iBAaF,+BAAA,CACd,KAA4B,EAArB,qBAAqB"}
|
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"page-scope-context-manager.mjs","names":[],"sources":["../../../src/internal/otel/page-scope-context-manager.ts"],"sourcesContent":["import type { Context } from \"@opentelemetry/api\";\nimport { ROOT_CONTEXT } from \"@opentelemetry/api\";\nimport { ZoneContextManager } from \"@opentelemetry/context-zone\";\n\n/**\n * `ZoneContextManager` returns `ROOT_CONTEXT` whenever `context.active()` is\n * called outside a zone the manager itself created — which in practice is\n * most of the time:\n *\n * - app code calling `fetch(...)` from a `useEffect`\n * - `PerformanceObserver` callbacks (long-task, paint timing, …)\n * - top-level await / module init code\n * - listeners attached before the SDK booted\n *\n * Spans created from those code paths default to \"no parent\" → root, which\n * is why a single page load produces N disjoint traces (one per\n * instrumentation) instead of one waterfall.\n *\n * This subclass adds a single fallback: if the underlying zone lookup yields\n * `ROOT_CONTEXT`, return the page-scope context instead. The page-scope\n * context is built once at SDK init from the `<meta name=\"traceparent\">`\n * tag emitted by the SSR layout. If the meta tag is absent the page scope\n * stays at `ROOT_CONTEXT` and the manager behaves like a stock\n * `ZoneContextManager`.\n */\nexport class PageScopeContextManager extends ZoneContextManager {\n private pageScope: Context = ROOT_CONTEXT;\n\n setPageScope(ctx: Context): void {\n this.pageScope = ctx;\n }\n\n override active(): Context {\n const ctx = super.active();\n return ctx === ROOT_CONTEXT ? this.pageScope : ctx;\n }\n}\n"],"mappings":"yGAyBA,IAAa,wBAAb,cAA6C,kBAAmB,CAC9D,UAA6B,aAE7B,aAAa,IAAoB,CAC/B,KAAK,UAAY,GACnB,CAEA,QAA2B,CACzB,IAAM,IAAM,MAAM,OAAO,EACzB,OAAO,MAAQ,aAAe,KAAK,UAAY,GACjD,CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"propagation.d.mts","names":[],"sources":["../../../src/internal/otel/propagation.ts"],"mappings":";;;;;AAsCA;;;;AAAsD;;;;;;;;;iBAAtC,2BAAA,IAA+B,OAAO"}
|
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"web-vitals.d.mts","names":[],"sources":["../../../src/internal/otel/web-vitals.ts"],"mappings":";;;UAmBiB,cAAA;;AAAjB;;;;EAME,KAAA,QAAa,OAAA;EAAA;EAEb,KAAA,EAAO,KAAK;EAAL;;;;AAMyB;EAAhC,YAAA,IAAgB,QAAA;AAAA;;;AAgBoC;;;;;;;;;;;iBAAtC,gBAAA,CAAiB,KAAqB,EAAd,cAAc"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"web-vitals.mjs","names":[],"sources":["../../../src/internal/otel/web-vitals.ts"],"sourcesContent":["import type { Histogram, Meter } from \"@opentelemetry/api\";\nimport { onCLS, onFCP, onINP, onLCP, onTTFB } from \"web-vitals\";\n\nconst ATTR_URL_PATH = \"url.path\" as const;\nconst MS_TO_S = 1 / 1000;\n\n/**\n * Vitals that fire during the page-load sequence — once we've received\n * all three we issue a one-shot flush so short visits still export\n * data. CLS and INP fire on `visibilitychange` which the kernel\n * already auto-flushes on, so they don't need their own trigger.\n */\nconst LOAD_VITALS = new Set([\"TTFB\", \"FCP\", \"LCP\"]);\n\ninterface VitalEntry {\n histogram: Histogram;\n toSeconds: boolean;\n}\n\nexport interface WebVitalsInput {\n /**\n * Called after the page-load vitals have all fired. The kernel passes\n * its own `flush()` so short-visit exports don't depend on the\n * `visibilitychange` listener firing.\n */\n flush: () => Promise<void>;\n /** OTel meter from the kernel's private provider. */\n meter: Meter;\n /**\n * Pathname → low-cardinality route template (e.g. `/blog/[slug]`).\n * Without this, dynamic routes produce one unique `url.path` label\n * per visited slug — a metric cardinality hazard.\n */\n resolveRoute?: (pathname: string) => string | undefined;\n}\n\n/**\n * Subscribes to Core Web Vitals (LCP/FCP/TTFB/INP/CLS) and records OTel\n * histograms. Time-based vitals are converted to seconds per\n * observability convention; CLS is dimensionless and recorded as-is.\n *\n * Each record carries `web_vital.rating`, `web_vital.navigation_type`,\n * and `url.path` (templated when `resolveRoute` is provided). Browser\n * and device dimensions come from the resource attached to the\n * MeterProvider, so every histogram datapoint already carries them.\n *\n * Bucket boundaries match the rum.ts SDK — same dashboards, same\n * histograms, same percentile shape.\n */\nexport function captureWebVitals(input: WebVitalsInput): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n const { meter, resolveRoute, flush } = input;\n\n const lcp = meter.createHistogram(\"web_vitals.lcp\", {\n description: \"Largest Contentful Paint\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [0.5, 1, 1.5, 2, 2.5, 3, 4, 5, 7.5, 10],\n },\n });\n const fcp = meter.createHistogram(\"web_vitals.fcp\", {\n description: \"First Contentful Paint\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [0.5, 1, 1.5, 1.8, 2, 2.5, 3, 4, 5, 7.5],\n },\n });\n const ttfb = meter.createHistogram(\"web_vitals.ttfb\", {\n description: \"Time to First Byte\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [0.1, 0.2, 0.3, 0.5, 0.8, 1, 1.5, 2, 3, 5],\n },\n });\n const inp = meter.createHistogram(\"web_vitals.inp\", {\n description: \"Interaction to Next Paint\",\n unit: \"s\",\n advice: {\n explicitBucketBoundaries: [\n 0.05, 0.1, 0.15, 0.2, 0.3, 0.4, 0.5, 0.75, 1, 2,\n ],\n },\n });\n const cls = meter.createHistogram(\"web_vitals.cls\", {\n description: \"Cumulative Layout Shift\",\n unit: \"1\",\n advice: {\n explicitBucketBoundaries: [\n 0.01, 0.025, 0.05, 0.075, 0.1, 0.15, 0.2, 0.25, 0.5, 1,\n ],\n },\n });\n\n const vitals: Record<string, VitalEntry> = {\n LCP: { histogram: lcp, toSeconds: true },\n FCP: { histogram: fcp, toSeconds: true },\n TTFB: { histogram: ttfb, toSeconds: true },\n INP: { histogram: inp, toSeconds: true },\n CLS: { histogram: cls, toSeconds: false },\n };\n\n const received = new Set<string>();\n let flushed = false;\n\n function report(metric: {\n name: string;\n value: number;\n rating?: string;\n navigationType?: string;\n }): void {\n const entry = vitals[metric.name];\n if (!entry) {\n return;\n }\n const value = entry.toSeconds ? metric.value * MS_TO_S : metric.value;\n const pathname = window.location.pathname;\n const route = resolveRoute?.(pathname) ?? pathname;\n\n entry.histogram.record(value, {\n ...(metric.rating ? { \"web_vital.rating\": metric.rating } : {}),\n ...(metric.navigationType\n ? { \"web_vital.navigation_type\": metric.navigationType }\n : {}),\n [ATTR_URL_PATH]: route,\n });\n\n if (!flushed && LOAD_VITALS.has(metric.name)) {\n received.add(metric.name);\n if (received.size === LOAD_VITALS.size) {\n flushed = true;\n flush().catch(() => undefined);\n }\n }\n }\n\n onLCP(report);\n onCLS(report);\n onINP(report);\n onTTFB(report);\n onFCP(report);\n}\n"],"mappings":"uDAGA,MASM,YAAc,IAAI,IAAI,CAAC,OAAQ,MAAO,KAAK,CAAC,EAqClD,SAAgB,iBAAiB,MAA6B,CAC5D,GAAI,OAAO,OAAW,IACpB,OAGF,GAAM,CAAE,MAAO,aAAc,OAAU,MAEjC,IAAM,MAAM,gBAAgB,iBAAkB,CAClD,YAAa,2BACb,KAAM,IACN,OAAQ,CACN,yBAA0B,CAAC,GAAK,EAAG,IAAK,EAAG,IAAK,EAAG,EAAG,EAAG,IAAK,EAAE,CAClE,CACF,CAAC,EACK,IAAM,MAAM,gBAAgB,iBAAkB,CAClD,YAAa,yBACb,KAAM,IACN,OAAQ,CACN,yBAA0B,CAAC,GAAK,EAAG,IAAK,IAAK,EAAG,IAAK,EAAG,EAAG,EAAG,GAAG,CACnE,CACF,CAAC,EACK,KAAO,MAAM,gBAAgB,kBAAmB,CACpD,YAAa,qBACb,KAAM,IACN,OAAQ,CACN,yBAA0B,CAAC,GAAK,GAAK,GAAK,GAAK,GAAK,EAAG,IAAK,EAAG,EAAG,CAAC,CACrE,CACF,CAAC,EACK,IAAM,MAAM,gBAAgB,iBAAkB,CAClD,YAAa,4BACb,KAAM,IACN,OAAQ,CACN,yBAA0B,CACxB,IAAM,GAAK,IAAM,GAAK,GAAK,GAAK,GAAK,IAAM,EAAG,CAChD,CACF,CACF,CAAC,EACK,IAAM,MAAM,gBAAgB,iBAAkB,CAClD,YAAa,0BACb,KAAM,IACN,OAAQ,CACN,yBAA0B,CACxB,IAAM,KAAO,IAAM,KAAO,GAAK,IAAM,GAAK,IAAM,GAAK,CACvD,CACF,CACF,CAAC,EAEK,OAAqC,CACzC,IAAK,CAAE,UAAW,IAAK,UAAW,EAAK,EACvC,IAAK,CAAE,UAAW,IAAK,UAAW,EAAK,EACvC,KAAM,CAAE,UAAW,KAAM,UAAW,EAAK,EACzC,IAAK,CAAE,UAAW,IAAK,UAAW,EAAK,EACvC,IAAK,CAAE,UAAW,IAAK,UAAW,EAAM,CAC1C,EAEM,SAAW,IAAI,IACjB,QAAU,GAEd,SAAS,OAAO,OAKP,CACP,IAAM,MAAQ,OAAO,OAAO,MAC5B,GAAI,CAAC,MACH,OAEF,IAAM,MAAQ,MAAM,UAAY,OAAO,MAAQ,KAAU,OAAO,MAC1D,SAAW,OAAO,SAAS,SAC3B,MAAQ,eAAe,QAAQ,GAAK,SAE1C,MAAM,UAAU,OAAO,MAAO,CAC5B,GAAI,OAAO,OAAS,CAAE,mBAAoB,OAAO,MAAO,EAAI,CAAC,EAC7D,GAAI,OAAO,eACP,CAAE,4BAA6B,OAAO,cAAe,EACrD,CAAC,EACJ,WAAgB,KACnB,CAAC,EAEG,CAAC,SAAW,YAAY,IAAI,OAAO,IAAI,IACzC,SAAS,IAAI,OAAO,IAAI,EACpB,SAAS,OAAS,YAAY,OAChC,QAAU,GACV,MAAM,EAAE,UAAY,IAAA,EAAS,GAGnC,CAEA,MAAM,MAAM,EACZ,MAAM,MAAM,EACZ,MAAM,MAAM,EACZ,OAAO,MAAM,EACb,MAAM,MAAM,CACd"}
|
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"plugin-runtime.mjs","names":[],"sources":["../../src/internal/plugin-runtime.ts"],"sourcesContent":["import {\n type ConsentState,\n PLUGIN_MANIFEST,\n type PluginKey,\n} from \"@interfere/types/sdk/plugins/manifest\";\nimport type { RemotePluginConfig } from \"@interfere/types/sdk/remote-config\";\n\nimport errorsPlugin from \"../plugins/errors.js\";\nimport {\n loadPlugin,\n type PluginOverrides,\n resolveFeatures,\n} from \"../plugins/lib/loader.js\";\nimport type { PluginCleanup, PluginContext } from \"../plugins/lib/types.js\";\nimport { createLogger } from \"../util/log.js\";\nimport {\n getPluginConsentCategory,\n hasConsentChanged,\n isConsentAllowed,\n resolveGrantedConsent,\n} from \"./consent.js\";\n\nconst log = createLogger(\"plugin-runtime\");\n\nexport class PluginRuntime {\n private readonly activeCleanups = new Map<PluginKey, PluginCleanup>();\n private readonly pending = new Set<Promise<void>>();\n private readonly context: PluginContext;\n private readonly features: Record<PluginKey, boolean>;\n private remoteConfig: RemotePluginConfig = {};\n private consentState: ConsentState | null;\n private syncVersion = 0;\n private disposed = false;\n\n constructor(\n context: PluginContext,\n overrides: PluginOverrides | undefined,\n initialConsent: ConsentState | undefined\n ) {\n this.context = context;\n this.features = resolveFeatures(overrides);\n this.consentState = initialConsent ?? null;\n }\n\n getConsent(): ConsentState | null {\n return this.consentState;\n }\n\n setConsent(nextConsent?: ConsentState): void {\n const nextState = resolveGrantedConsent(nextConsent);\n if (!hasConsentChanged(this.consentState, nextState)) {\n return;\n }\n\n this.consentState = nextState;\n this.sync();\n }\n\n resetConsent(): void {\n if (!hasConsentChanged(this.consentState, null)) {\n return;\n }\n\n this.consentState = null;\n this.sync();\n }\n\n applyRemoteConfig(config: RemotePluginConfig): void {\n this.remoteConfig = config;\n this.sync();\n }\n\n start(): void {\n if (this.shouldEnablePlugin(\"errors\")) {\n const cleanup = errorsPlugin.setup(this.context);\n if (cleanup) {\n this.activeCleanups.set(\"errors\", cleanup);\n }\n }\n\n this.sync();\n }\n\n async dispose(): Promise<void> {\n this.disposed = true;\n this.syncVersion += 1;\n\n for (const key of this.activeCleanups.keys()) {\n this.deactivate(key);\n }\n\n await Promise.allSettled(this.pending);\n }\n\n private shouldEnablePlugin(key: PluginKey): boolean {\n return (\n this.features[key] &&\n this.remoteConfig[key] !== false &&\n isConsentAllowed(getPluginConsentCategory(key), this.consentState)\n );\n }\n\n private deactivate(key: PluginKey): void {\n const cleanup = this.activeCleanups.get(key);\n if (!cleanup) {\n return;\n }\n\n try {\n cleanup();\n } catch {\n log.warn(\"cleanup failed for %s\", key);\n }\n\n this.activeCleanups.delete(key);\n }\n\n private async activate(key: PluginKey): Promise<void> {\n if (this.activeCleanups.has(key) || !this.shouldEnablePlugin(key)) {\n return;\n }\n\n const version = this.syncVersion;\n const cleanup = await loadPlugin(key, this.context);\n if (!cleanup) {\n return;\n }\n\n const staleSync = version !== this.syncVersion;\n if (staleSync || !this.shouldEnablePlugin(key)) {\n cleanup();\n return;\n }\n\n this.activeCleanups.set(key, cleanup);\n }\n\n private sync(): void {\n if (this.disposed) {\n return;\n }\n\n this.syncVersion += 1;\n\n for (const plugin of PLUGIN_MANIFEST) {\n if (this.shouldEnablePlugin(plugin.name)) {\n if (plugin.name === \"errors\") {\n if (!this.activeCleanups.has(\"errors\")) {\n const cleanup = errorsPlugin.setup(this.context);\n if (cleanup) {\n this.activeCleanups.set(\"errors\", cleanup);\n }\n }\n continue;\n }\n\n const p = this.activate(plugin.name).catch(() => {\n log.warn(\"non-critical plugin loading failed\");\n });\n this.pending.add(p);\n p.finally(() => this.pending.delete(p));\n continue;\n }\n\n this.deactivate(plugin.name);\n }\n }\n}\n"],"mappings":"4UAsBA,MAAM,IAAM,aAAa,gBAAgB,EAEzC,IAAa,cAAb,KAA2B,CACzB,eAAkC,IAAI,IACtC,QAA2B,IAAI,IAC/B,QACA,SACA,aAA2C,CAAC,EAC5C,aACA,YAAsB,EACtB,SAAmB,GAEnB,YACE,QACA,UACA,eACA,CACA,KAAK,QAAU,QACf,KAAK,SAAW,gBAAgB,SAAS,EACzC,KAAK,aAAe,gBAAkB,IACxC,CAEA,YAAkC,CAChC,OAAO,KAAK,YACd,CAEA,WAAW,YAAkC,CAC3C,IAAM,UAAY,sBAAsB,WAAW,EAC9C,kBAAkB,KAAK,aAAc,SAAS,IAInD,KAAK,aAAe,UACpB,KAAK,KAAK,EACZ,CAEA,cAAqB,CACd,kBAAkB,KAAK,aAAc,IAAI,IAI9C,KAAK,aAAe,KACpB,KAAK,KAAK,EACZ,CAEA,kBAAkB,OAAkC,CAClD,KAAK,aAAe,OACpB,KAAK,KAAK,CACZ,CAEA,OAAc,CACZ,GAAI,KAAK,mBAAmB,QAAQ,EAAG,CACrC,IAAM,QAAU,aAAa,MAAM,KAAK,OAAO,EAC3C,SACF,KAAK,eAAe,IAAI,SAAU,OAAO,CAE7C,CAEA,KAAK,KAAK,CACZ,CAEA,MAAM,SAAyB,CAC7B,KAAK,SAAW,GAChB,KAAK,aAAe,EAEpB,IAAK,IAAM,OAAO,KAAK,eAAe,KAAK,EACzC,KAAK,WAAW,GAAG,EAGrB,MAAM,QAAQ,WAAW,KAAK,OAAO,CACvC,CAEA,mBAA2B,IAAyB,CAClD,OACE,KAAK,SAAS,MACd,KAAK,aAAa,OAAS,IAC3B,iBAAiB,yBAAyB,GAAG,EAAG,KAAK,YAAY,CAErE,CAEA,WAAmB,IAAsB,CACvC,IAAM,QAAU,KAAK,eAAe,IAAI,GAAG,EACtC,WAIL,IAAI,CACF,QAAQ,CACV,MAAQ,CACN,IAAI,KAAK,wBAAyB,GAAG,CACvC,CAEA,KAAK,eAAe,OAAO,GAAG,CAF9B,CAGF,CAEA,MAAc,SAAS,IAA+B,CACpD,GAAI,KAAK,eAAe,IAAI,GAAG,GAAK,CAAC,KAAK,mBAAmB,GAAG,EAC9D,OAGF,IAAM,QAAU,KAAK,YACf,QAAU,MAAM,WAAW,IAAK,KAAK,OAAO,EAC7C,WAKL,IADkB,UAAY,KAAK,aAClB,CAAC,KAAK,mBAAmB,GAAG,EAAG,CAC9C,QAAQ,EACR,MACF,CAEA,KAAK,eAAe,IAAI,IAAK,OAAO,CAFpC,CAGF,CAEA,MAAqB,CACf,SAAK,SAIT,MAAK,aAAe,EAEpB,IAAK,IAAM,UAAU,gBAAiB,CACpC,GAAI,KAAK,mBAAmB,OAAO,IAAI,EAAG,CACxC,GAAI,OAAO,OAAS,SAAU,CAC5B,GAAI,CAAC,KAAK,eAAe,IAAI,QAAQ,EAAG,CACtC,IAAM,QAAU,aAAa,MAAM,KAAK,OAAO,EAC3C,SACF,KAAK,eAAe,IAAI,SAAU,OAAO,CAE7C,CACA,QACF,CAEA,IAAM,EAAI,KAAK,SAAS,OAAO,IAAI,EAAE,UAAY,CAC/C,IAAI,KAAK,oCAAoC,CAC/C,CAAC,EACD,KAAK,QAAQ,IAAI,CAAC,EAClB,EAAE,YAAc,KAAK,QAAQ,OAAO,CAAC,CAAC,EACtC,QACF,CAEA,KAAK,WAAW,OAAO,IAAI,CAC7B,CAvBoB,CAwBtB,CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"react-context.d.mts","names":[],"sources":["../../src/internal/react-context.ts"],"mappings":";;;;;UASiB,qBAAA;EACf,OAAA;IACE,GAAA,IAAO,YAAA;IACP,GAAA,CAAI,KAAA,GAAQ,YAAA;EAAA;EAEd,MAAA;IACE,WAAA;IACA,SAAA;EAAA;EAEF,QAAA;IACE,GAAA,IAAO,cAAA;IACP,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA;EAAA;EAV/B;;;;;;EAkBA,MAAA,EAAQ,MAAA;EACR,OAAA;IACE,KAAA;IACA,WAAA;EAAA;AAAA;;;;;;;cAkBS,kBAAA,EAAoB,qBAMhC;;;;AAxBc;cA8BF,gBAAA,kBAAgB,OAAA,CAAA,qBAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"react-context.mjs","names":[],"sources":["../../src/internal/react-context.ts"],"sourcesContent":["\"use client\";\n\nimport type { IdentifyParams } from \"@interfere/types/sdk/identify\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\n\nimport { createContext } from \"react\";\n\nimport type { Kernel } from \"./kernel.js\";\n\nexport interface InterfereContextValue {\n consent: {\n get(): ConsentState | null;\n set(state?: ConsentState): void;\n };\n device: {\n getDeviceId(): string | null;\n getFpHash(): string | null;\n };\n identity: {\n get(): IdentifyParams | null;\n set(params: IdentifyParams): Promise<void>;\n };\n /**\n * The active kernel, or `null` when the SDK isn't initialized yet — for\n * example during server-side prerender, when `init()` is client-only.\n * Accessor objects (consent/device/identity/session) above stay safe to\n * call in either state; they no-op when the kernel is null.\n */\n kernel: Kernel | null;\n session: {\n getId(): string | null;\n getWindowId(): string | null;\n };\n}\n\nconst NULL_CONSENT = { get: () => null, set: () => undefined };\nconst NULL_DEVICE = { getDeviceId: () => null, getFpHash: () => null };\nconst NULL_IDENTITY = {\n get: () => null,\n set: () => Promise.resolve(),\n};\nconst NULL_SESSION = { getId: () => null, getWindowId: () => null };\n\n/**\n * Context value used when the provider is mounted without a kernel — every\n * accessor returns `null` and mutations are no-ops. Lets `useInterfere()`\n * work seamlessly across SSR/CSR boundaries (server gets nulls, client\n * gets real values once the kernel resolves).\n */\nexport const NULL_CONTEXT_VALUE: InterfereContextValue = {\n consent: NULL_CONSENT,\n device: NULL_DEVICE,\n identity: NULL_IDENTITY,\n kernel: null,\n session: NULL_SESSION,\n};\n\n/**\n * Lives in its own module so `provider.tsx` and `internal/capture-boundary.tsx`\n * can both import it without forming an import cycle.\n */\nexport const InterfereContext = createContext<InterfereContextValue | null>(\n null\n);\n"],"mappings":"8CAiDA,MAAa,mBAA4C,CACvD,QAAS,CAfY,QAAW,KAAM,QAAW,IAAA,EAexC,EACT,OAAQ,CAfY,gBAAmB,KAAM,cAAiB,IAetD,EACR,SAAU,CAdV,QAAW,KACX,QAAW,QAAQ,QAAQ,CAajB,EACV,OAAQ,KACR,QAAS,CAbY,UAAa,KAAM,gBAAmB,IAalD,CACX,EAMa,iBAAmB,cAC9B,IACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"sw.d.mts","names":[],"sources":["../../src/internal/sw.ts"],"mappings":";UAMiB,qBAAA;EAAA;;;;;;;EAQf,SAAA,IAAa,OAAA,EAAS,SAAS;AAAA;AAAA,UAGhB,SAAA;EACf,MAAA;EACA,IAAA;EAIA,GAAA;AAAA;;;;AAAG;AAQL;iBAAsB,qBAAA,CACpB,IAAA,GAAM,qBAAA,GACL,OAAO"}
|
package/dist/internal/sw.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.mts","names":[],"sources":["../../src/internal/version.ts"],"mappings":";cAEa,QAAA;AAAA,cACA,WAAA;AAAA,cACA,gBAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"version.mjs","names":["pkg.name","pkg.version"],"sources":["../../src/internal/version.ts"],"sourcesContent":["import pkg from \"../../package.json\" with { type: \"json\" };\n\nexport const SDK_NAME = pkg.name;\nexport const SDK_VERSION = pkg.version;\nexport const PRODUCER_VERSION = `${SDK_NAME}@${SDK_VERSION}`;\n"],"mappings":"yCAEA,MAAa,SAAWA,KACX,YAAcC,QACd,iBAAmB,GAAG,SAAS,GAAG"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"wrapper-singleton.d.mts","names":[],"sources":["../../src/internal/wrapper-singleton.ts"],"mappings":";;;;;UAaiB,gBAAA;EACf,KAAA,IAAS,OAAA;EACT,OAAA;IACE,GAAA,IAAO,YAAA;IACP,GAAA,CAAI,KAAA,GAAQ,YAAA;EAAA;EAEd,SAAA,IAAa,MAAA;EACb,eAAA,IAAmB,MAAA;EACnB,QAAA;IACE,GAAA,IAAO,cAAA;IACP,GAAA,CAAI,MAAA,EAAQ,cAAA,GAAiB,OAAA;EAAA;EAE/B,IAAA,CAAK,IAAA,GAAO,aAAA,GAAgB,OAAA,CAAQ,MAAA;EACpC,iBAAA,CAAkB,QAAA;AAAA;AAAA,UAGH,qBAAA;EAJoB;;;;;EAUnC,aAAA;EAnBS;;;;;EAyBT,eAAe;AAAA;;;;;;;;;;;iBAaD,sBAAA,CACd,KAAA,EAAO,qBAAA,GACN,gBAAgB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"wrapper-singleton.mjs","names":[],"sources":["../../src/internal/wrapper-singleton.ts"],"sourcesContent":["import type { IdentifyParams } from \"@interfere/types/sdk/identify\";\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\n\nimport { isBotUserAgent } from \"../util/bot.js\";\nimport {\n createKernel,\n isEnabledByEnvironment,\n type Kernel,\n type KernelInternalOptions,\n type KernelOptions,\n} from \"./kernel.js\";\nimport { registerKernel, unregisterKernel } from \"./kernel-registry.js\";\n\nexport interface WrapperSingleton {\n close(): Promise<void>;\n consent: {\n get(): ConsentState | null;\n set(value?: ConsentState): void;\n };\n getKernel(): Kernel;\n getKernelOrNull(): Kernel | null;\n identity: {\n get(): IdentifyParams | null;\n set(params: IdentifyParams): Promise<void>;\n };\n init(opts?: KernelOptions): Promise<Kernel | null>;\n subscribeToKernel(listener: () => void): () => void;\n}\n\nexport interface WrapperSingletonInput {\n /**\n * Surfaced in the \"kernel not initialized\" error so the message points\n * customers at the right entry file (e.g. \"instrumentation-client\" /\n * \"main.ts\"). Single concrete fact — no other behavior change.\n */\n initEntryName: string;\n /**\n * Wrapper SDK identifier injected as the first entry in\n * `__INTERFERE_SDK_STACK__` (e.g. `@interfere/next@10.0.0`). Threaded\n * through to the kernel so the OTel resource carries the full stack.\n */\n producerVersion: string;\n}\n\n/**\n * Per-wrapper kernel-singleton lifecycle. The next/vite/etc wrappers used\n * to copy the same ~100 lines verbatim (init/close/getKernel/subscribe\n * /consent/identity); this is the single source of truth so a fix once\n * applies everywhere.\n *\n * Each wrapper holds its own `WrapperSingleton` in module scope so the\n * kernel is per-wrapper, not per-process. (Microfrontends can run two\n * different wrappers on the same page; each gets its own kernel.)\n */\nexport function createWrapperSingleton(\n input: WrapperSingletonInput\n): WrapperSingleton {\n let kernel: Kernel | null = null;\n let pending: Promise<Kernel | null> | null = null;\n const listeners = new Set<() => void>();\n\n function emit(): void {\n for (const listener of listeners) {\n listener();\n }\n }\n\n return {\n init(opts: KernelOptions = {}): Promise<Kernel | null> {\n if (kernel) {\n return Promise.resolve(kernel);\n }\n if (pending) {\n return pending;\n }\n if (!(opts.enabled ?? isEnabledByEnvironment())) {\n return Promise.resolve(null);\n }\n if (isBotUserAgent()) {\n return Promise.resolve(null);\n }\n\n const internalOpts: KernelInternalOptions = {\n ...opts,\n _wrapperVersions: [input.producerVersion],\n };\n pending = createKernel({ opts: internalOpts })\n .then((k) => {\n kernel = k;\n if (k) {\n registerKernel(k);\n }\n emit();\n return k;\n })\n .finally(() => {\n pending = null;\n });\n\n return pending;\n },\n\n getKernel(): Kernel {\n if (!kernel) {\n throw new Error(\n `Interfere SDK not initialized. Call init() from your ${input.initEntryName} entrypoint.`\n );\n }\n return kernel;\n },\n\n getKernelOrNull(): Kernel | null {\n return kernel;\n },\n\n async close(): Promise<void> {\n if (!kernel) {\n return;\n }\n const previous = kernel;\n await previous.dispose();\n kernel = null;\n unregisterKernel(previous);\n emit();\n },\n\n subscribeToKernel(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n\n consent: {\n get: () => kernel?.consent.get() ?? null,\n set: (value) => kernel?.consent.set(value),\n },\n\n identity: {\n get: () => kernel?.identity.get() ?? null,\n set: (params) => kernel?.identity.set(params) ?? Promise.resolve(),\n },\n };\n}\n"],"mappings":"6KAsDA,SAAgB,uBACd,MACkB,CAClB,IAAI,OAAwB,KACxB,QAAyC,KACvC,UAAY,IAAI,IAEtB,SAAS,MAAa,CACpB,IAAK,IAAM,YAAY,UACrB,SAAS,CAEb,CAEA,MAAO,CACL,KAAK,KAAsB,CAAC,EAA2B,CA+BrD,OA9BI,OACK,QAAQ,QAAQ,MAAM,EAE3B,UAGA,EAAE,KAAK,SAAW,uBAAuB,IAGzC,eAAe,EACV,QAAQ,QAAQ,IAAI,GAO7B,QAAU,aAAa,CAAE,KAAM,CAH7B,GAAG,KACH,iBAAkB,CAAC,MAAM,eAAe,CAEA,CAAE,CAAC,EAC1C,KAAM,IACL,OAAS,EACL,GACF,eAAe,CAAC,EAElB,KAAK,EACE,EACR,EACA,YAAc,CACb,QAAU,IACZ,CAAC,EAEI,SACT,EAEA,WAAoB,CAClB,GAAI,CAAC,OACH,MAAU,MACR,wDAAwD,MAAM,cAAc,aAC9E,EAEF,OAAO,MACT,EAEA,iBAAiC,CAC/B,OAAO,MACT,EAEA,MAAM,OAAuB,CAC3B,GAAI,CAAC,OACH,OAEF,IAAM,SAAW,OACjB,MAAM,SAAS,QAAQ,EACvB,OAAS,KACT,iBAAiB,QAAQ,EACzB,KAAK,CACP,EAEA,kBAAkB,SAAkC,CAElD,OADA,UAAU,IAAI,QAAQ,MACT,CACX,UAAU,OAAO,QAAQ,CAC3B,CACF,EAEA,QAAS,CACP,QAAW,QAAQ,QAAQ,IAAI,GAAK,KACpC,IAAM,OAAU,QAAQ,QAAQ,IAAI,KAAK,CAC3C,EAEA,SAAU,CACR,QAAW,QAAQ,SAAS,IAAI,GAAK,KACrC,IAAM,QAAW,QAAQ,SAAS,IAAI,MAAM,GAAK,QAAQ,QAAQ,CACnE,CACF,CACF"}
|
package/dist/package.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"package.mjs","names":[],"sources":["../package.json"],"sourcesContent":[""],"mappings":""}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.mts","names":[],"sources":["../../src/plugins/errors.ts"],"mappings":";;;cAwDa,YAAA,EAAc,MAgF1B"}
|
|
@@ -1 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../src/plugins/lib/types.ts"],"mappings":";;;;KAIY,aAAA;AAAA,UAEK,aAAA;EACf,YAAA;EACA,eAAA,CACE,KAAA,EAAO,KAAA,GAAQ,iBAAA,EACf,IAAA,EAAM,mBAAA;AAAA;AAAA,UAIO,MAAA;EAAA,SACN,IAAA;EACT,KAAA,CAAM,GAAA,EAAK,aAAA,GAAgB,aAAa;AAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logs.d.mts","names":[],"sources":["../../src/plugins/logs.ts"],"mappings":";;;;;AAyBA;;;;AA0BC;cA1BY,UAAA,EAAY,MA0BxB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logs.mjs","names":[],"sources":["../../src/plugins/logs.ts"],"sourcesContent":["import { SeverityNumber } from \"@opentelemetry/api-logs\";\n\nimport { type ConsoleLevel, onConsoleCall } from \"../internal/console-patch.js\";\nimport { activeKernel } from \"../internal/kernel-registry.js\";\nimport { safeStringify } from \"../util/stringify.js\";\nimport type { Plugin } from \"./lib/types.js\";\n\nconst LEVEL_TO_SEVERITY: Record<\n ConsoleLevel,\n { number: SeverityNumber; text: string }\n> = {\n debug: { number: SeverityNumber.DEBUG, text: \"DEBUG\" },\n info: { number: SeverityNumber.INFO, text: \"INFO\" },\n log: { number: SeverityNumber.INFO, text: \"INFO\" },\n warn: { number: SeverityNumber.WARN, text: \"WARN\" },\n error: { number: SeverityNumber.ERROR, text: \"ERROR\" },\n};\n\n/**\n * Subscribes to the central console patch so string-only console calls\n * land as OTel `LogRecord`s. Error-bearing calls are skipped: the\n * `errors` plugin's subscriber on the same patch handles those as\n * exceptions. Class boundary stays firm: errors flow as exceptions,\n * strings flow as logs.\n */\nexport const logsPlugin: Plugin = {\n name: \"logs\",\n\n setup() {\n const unsubscribe = onConsoleCall((level, args) => {\n if (args.some((a) => a instanceof Error)) {\n return;\n }\n const kernel = activeKernel();\n if (!kernel) {\n return;\n }\n const sev = LEVEL_TO_SEVERITY[level];\n kernel.recordLog({\n severityText: sev.text,\n severityNumber: sev.number,\n body:\n args.length === 1\n ? safeStringify(args[0])\n : args.map(safeStringify).join(\" \"),\n attributes: { \"console.level\": level },\n });\n });\n\n return unsubscribe;\n },\n};\n\nexport default logsPlugin;\n"],"mappings":"wNAOA,MAAM,kBAGF,CACF,MAAO,CAAE,OAAQ,eAAe,MAAO,KAAM,OAAQ,EACrD,KAAM,CAAE,OAAQ,eAAe,KAAM,KAAM,MAAO,EAClD,IAAK,CAAE,OAAQ,eAAe,KAAM,KAAM,MAAO,EACjD,KAAM,CAAE,OAAQ,eAAe,KAAM,KAAM,MAAO,EAClD,MAAO,CAAE,OAAQ,eAAe,MAAO,KAAM,OAAQ,CACvD,EASa,WAAqB,CAChC,KAAM,OAEN,OAAQ,CAqBN,OApBoB,eAAe,MAAO,OAAS,CACjD,GAAI,KAAK,KAAM,GAAM,aAAa,KAAK,EACrC,OAEF,IAAM,OAAS,aAAa,EAC5B,GAAI,CAAC,OACH,OAEF,IAAM,IAAM,kBAAkB,OAC9B,OAAO,UAAU,CACf,aAAc,IAAI,KAClB,eAAgB,IAAI,OACpB,KACE,KAAK,SAAW,EACZ,cAAc,KAAK,EAAE,EACrB,KAAK,IAAI,aAAa,EAAE,KAAK,GAAG,EACtC,WAAY,CAAE,gBAAiB,KAAM,CACvC,CAAC,CACH,CAEiB,CACnB,CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rage-clicks.d.mts","names":[],"sources":["../../src/plugins/rage-clicks.ts"],"mappings":";;;cAgCa,gBAAA,EAAkB,MA8D9B"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/plugins/replay/index.ts"],"mappings":";;;cAea,YAAA,EAAc,MAwF1B"}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|