@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":"recorder.d.mts","names":[],"sources":["../../../src/plugins/replay/recorder.ts"],"mappings":";UAQiB,cAAA;EACf,IAAA;EACA,gBAAgB;AAAA;AAAA,iBAGI,mBAAA,CACpB,OAAA,GAAU,KAAA,qBACT,OAAO,CAAC,cAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"recorder.mjs","names":[],"sources":["../../../src/plugins/replay/recorder.ts"],"sourcesContent":["import { createLogger } from \"../../util/log.js\";\n\nconst log = createLogger(\"replay\");\n\nconst MOUSEMOVE_SAMPLE_MS = 50;\nconst SCROLL_SAMPLE_MS = 150;\nconst MEDIA_SAMPLE_MS = 800;\n\nexport interface ReplayRecorder {\n stop(): void;\n takeFullSnapshot(): void;\n}\n\nexport async function startReplayRecorder(\n onEvent: (event: unknown) => void\n): Promise<ReplayRecorder | null> {\n try {\n const rrweb = await import(\"rrweb\");\n const stop =\n rrweb.record({\n emit: onEvent,\n recordAfter: \"load\",\n inlineStylesheet: true,\n slimDOMOptions: \"all\",\n sampling: {\n mousemove: MOUSEMOVE_SAMPLE_MS,\n scroll: SCROLL_SAMPLE_MS,\n media: MEDIA_SAMPLE_MS,\n },\n }) ?? null;\n\n return {\n stop: () => {\n stop?.();\n },\n takeFullSnapshot: () => {\n try {\n rrweb.record.takeFullSnapshot?.(true);\n } catch (error) {\n log.warn(\"takeFullSnapshot failed: %o\", error);\n }\n },\n };\n } catch {\n log.error(\"rrweb failed to load, replay disabled\");\n return null;\n }\n}\n"],"mappings":"6CAEA,MAAM,IAAM,aAAa,QAAQ,EAWjC,eAAsB,oBACpB,QACgC,CAChC,GAAI,CACF,IAAM,MAAQ,MAAM,OAAO,SACrB,KACJ,MAAM,OAAO,CACX,KAAM,QACN,YAAa,OACb,iBAAkB,GAClB,eAAgB,MAChB,SAAU,CACR,UAAW,GACX,OAAQ,IACR,MAAO,GACT,CACF,CAAC,GAAK,KAER,MAAO,CACL,SAAY,CACV,OAAO,CACT,EACA,qBAAwB,CACtB,GAAI,CACF,MAAM,OAAO,mBAAmB,EAAI,CACtC,OAAS,MAAO,CACd,IAAI,KAAK,8BAA+B,KAAK,CAC/C,CACF,CACF,CACF,MAAQ,CAEN,OADA,IAAI,MAAM,uCAAuC,EAC1C,IACT,CACF"}
|
package/dist/provider.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.mts","names":[],"sources":["../src/provider.tsx"],"mappings":";;;;;;UAmBU,sBAAA,SAA+B,iBAAA;EACvC,OAAA,GAAU,YAAA;EADF;;;;;;;;;;;;;;;AAyBM;EAPd,aAAA;EAU+B;;;;;;EAH/B,MAAA,EAAQ,MAAA;AAAA;AAAA,iBAGM,iBAAA;EACd,MAAA;EACA,QAAA;EACA,OAAA;EACA;AAAA,GACC,sBAAA,GAAyB,SAAA;AAAA,iBAwBZ,YAAA,IAAgB,qBAAqB;AAAA,iBAQrC,UAAA"}
|
package/dist/provider.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"provider.mjs","names":[],"sources":["../src/provider.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ConsentState } from \"@interfere/types/sdk/plugins/manifest\";\n\nimport {\n type PropsWithChildren,\n type ReactNode,\n useContext,\n useEffect,\n} from \"react\";\n\nimport { CaptureBoundary } from \"./internal/capture-boundary.js\";\nimport type { Kernel } from \"./internal/kernel.js\";\nimport type { InterfereContextValue } from \"./internal/react-context.js\";\nimport {\n InterfereContext,\n NULL_CONTEXT_VALUE,\n} from \"./internal/react-context.js\";\n\ninterface InterfereProviderProps extends PropsWithChildren {\n consent?: ConsentState | undefined;\n /**\n * Auto-install an internal capture boundary around `children` so\n * render-phase React errors are reported even when the host app has no\n * `ErrorBoundary` / `error.tsx` of its own.\n *\n * The internal boundary is transparent: it captures the error and then\n * re-throws so upstream boundaries (the customer's own, Next.js's\n * `error.tsx` / `global-error.tsx`, or React's default unmount) keep full\n * control of what the user sees. Safe to leave enabled.\n *\n * Pass `false` only if you explicitly don't want automatic capture of\n * render-phase errors — for example, if you've already wired\n * {@link reactErrorHandler} into `createRoot()`.\n *\n * @default true\n */\n errorBoundary?: boolean;\n /**\n * The active kernel from `init()`. Pass `null` (or omit on the server)\n * when the SDK isn't initialized — useful during SSR/SSG where `init()`\n * runs client-side only. The provider mounts with null-safe accessors\n * and switches to the real kernel once it's available.\n */\n kernel: Kernel | null;\n}\n\nexport function InterfereProvider({\n kernel,\n children,\n consent,\n errorBoundary = true,\n}: InterfereProviderProps): ReactNode {\n useEffect(() => {\n kernel?.syncConsent(consent);\n }, [kernel, consent]);\n\n const value: InterfereContextValue = kernel\n ? {\n consent: kernel.consent,\n device: kernel.device,\n identity: kernel.identity,\n kernel,\n session: kernel.session,\n }\n : NULL_CONTEXT_VALUE;\n\n const body = errorBoundary ? (\n <CaptureBoundary>{children}</CaptureBoundary>\n ) : (\n children\n );\n\n return <InterfereContext value={value}>{body}</InterfereContext>;\n}\n\nexport function useInterfere(): InterfereContextValue {\n const ctx = useContext(InterfereContext);\n if (!ctx) {\n throw new Error(\"useInterfere must be used within <InterfereProvider>\");\n }\n return ctx;\n}\n\nexport function useSession(): string | null {\n return useInterfere().session.getId();\n}\n\nexport type { InterfereContextValue } from \"./internal/react-context.js\";\n// biome-ignore lint/performance/noBarrelFile: Re-export the context handle alongside the provider so consumers (custom error boundaries, the `@interfere/next` wrapper) can import everything from one entry.\nexport { InterfereContext } from \"./internal/react-context.js\";\n"],"mappings":"mOA+CA,SAAgB,kBAAkB,CAChC,OACA,SACA,QACA,cAAgB,IACoB,CAqBpC,OApBA,cAAgB,CACd,QAAQ,YAAY,OAAO,CAC7B,EAAG,CAAC,OAAQ,OAAO,CAAC,EAkBb,IAAC,iBAAD,CAAkB,MAhBY,OACjC,CACE,QAAS,OAAO,QAChB,OAAQ,OAAO,OACf,SAAU,OAAO,SACjB,OACA,QAAS,OAAO,OAClB,EACA,4BAES,cACX,IAAC,gBAAD,CAAkB,QAA0B,CAAA,EAE5C,QAG6D,CAAA,CACjE,CAEA,SAAgB,cAAsC,CACpD,IAAM,IAAM,WAAW,gBAAgB,EACvC,GAAI,CAAC,IACH,MAAU,MAAM,sDAAsD,EAExE,OAAO,GACT,CAEA,SAAgB,YAA4B,CAC1C,OAAO,aAAa,EAAE,QAAQ,MAAM,CACtC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"react-error-handler.d.mts","names":[],"sources":["../src/react-error-handler.ts"],"mappings":";;;;;AAKmD;;;;AAUnC;UADN,kBAAA;EACR,cAAc;AAAA;AAAA,KAGX,iBAAA,IAAqB,KAAA,WAAgB,IAAA,EAAM,kBAAkB;AAAA,UAEjD,0BAAA;EAF+B;;;AAAkB;AAElE;EAME,aAAA,GAAgB,iBAAA;;;;;;EAMhB,kBAAA,GAAqB,iBAAA;EANrB;;;;;EAYA,eAAA,GAAkB,iBAAA;AAAA;AAAA,UAGH,wBAAA,SAAiC,0BAAA;EAAjC;;;;;;;;;EAUf,MAAA,GAAS,MAAA,UAAgB,MAAA;AAAA;AAAA,UAGjB,sBAAA;EACR,aAAA,EAAe,iBAAA;EACf,kBAAA,EAAoB,iBAAA;EACpB,eAAA,EAAiB,iBAAA;AAAA;;;;;;;;;;;;;;AAAiB;AAqCpC;;;;;;;;AAEyB;;;iBAFT,iBAAA,CACd,OAAA,GAAS,wBAAA,GACR,sBAAsB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"react-error-handler.mjs","names":[],"sources":["../src/react-error-handler.ts"],"sourcesContent":["\"use client\";\n\nimport { MECHANISM_TYPE } from \"@interfere/types/sdk/errors\";\n\nimport { captureReactError } from \"./internal/capture.js\";\nimport type { Kernel } from \"./internal/kernel.js\";\n\n/**\n * Matches the shape React 19 passes to `onCaughtError`, `onUncaughtError`,\n * and `onRecoverableError` on `createRoot()` / `hydrateRoot()`.\n *\n * We intentionally keep this permissive — React has iterated on the exact\n * fields of `ErrorInfo` across 19.x minors and we only need `componentStack`.\n */\ninterface ReactRootErrorInfo {\n componentStack?: string | null | undefined;\n}\n\ntype RootErrorCallback = (error: unknown, info: ReactRootErrorInfo) => void;\n\nexport interface ReactErrorHandlerCallbacks {\n /**\n * Called when a React error boundary caught and handled an error. Interfere\n * reports it with `mechanism.handled: true`. Your callback runs after the\n * capture.\n */\n onCaughtError?: RootErrorCallback;\n /**\n * Called when concurrent React auto-recovered from an error by retrying\n * the render. Reported with `mechanism.synthetic: true` so the agent can\n * deprioritize — these often aren't user-facing bugs.\n */\n onRecoverableError?: RootErrorCallback;\n /**\n * Called when a render-phase error propagated past every boundary and\n * React unmounted (or fell back to its default UI). Reported with\n * `mechanism.handled: false` — this is the \"real\" uncaught case.\n */\n onUncaughtError?: RootErrorCallback;\n}\n\nexport interface ReactErrorHandlerOptions extends ReactErrorHandlerCallbacks {\n /**\n * The kernel to capture errors against. Pass either the kernel directly\n * or a getter — framework wrappers expose `getKernelOrNull()` for the\n * latter pattern, which lets you wire the handler before `init()`\n * resolves. Pass `getKernelOrNull` (not `getKernel`) — the throwing\n * variant breaks this contract since the handler runs in error-recovery\n * paths where rethrowing isn't useful. Capture is silently skipped when\n * no kernel is available.\n */\n kernel?: Kernel | (() => Kernel | null) | null;\n}\n\ninterface ReactRootErrorHandlers {\n onCaughtError: RootErrorCallback;\n onRecoverableError: RootErrorCallback;\n onUncaughtError: RootErrorCallback;\n}\n\nfunction resolveKernel(\n source: ReactErrorHandlerOptions[\"kernel\"]\n): Kernel | null {\n if (!source) {\n return null;\n }\n return typeof source === \"function\" ? source() : source;\n}\n\n/**\n * Creates the three error callbacks React 19's `createRoot()` / `hydrateRoot()`\n * accept, wired into Interfere's capture pipeline.\n *\n * ```ts\n * import { createRoot } from \"react-dom/client\";\n * import { getKernelOrNull } from \"@interfere/next\";\n * import { reactErrorHandler } from \"@interfere/react/react-error-handler\";\n *\n * createRoot(\n * document.getElementById(\"root\")!,\n * reactErrorHandler({ kernel: getKernelOrNull })\n * ).render(<App />);\n * ```\n *\n * Pass your own callbacks to observe errors without replacing the capture\n * behaviour — user callbacks run after Interfere has captured:\n *\n * ```ts\n * reactErrorHandler({\n * kernel: getKernelOrNull,\n * onUncaughtError: (err) => myLogger.fatal(err),\n * });\n * ```\n */\nexport function reactErrorHandler(\n options: ReactErrorHandlerOptions = {}\n): ReactRootErrorHandlers {\n const { kernel: kernelSource, ...callbacks } = options;\n return {\n onCaughtError(error, info) {\n if (error instanceof Error) {\n captureReactError(\n resolveKernel(kernelSource),\n error,\n info.componentStack,\n {\n type: MECHANISM_TYPE.react.caughtError,\n handled: true,\n }\n );\n }\n callbacks.onCaughtError?.(error, info);\n },\n onUncaughtError(error, info) {\n if (error instanceof Error) {\n captureReactError(\n resolveKernel(kernelSource),\n error,\n info.componentStack,\n {\n type: MECHANISM_TYPE.react.uncaughtError,\n handled: false,\n }\n );\n }\n callbacks.onUncaughtError?.(error, info);\n },\n onRecoverableError(error, info) {\n if (error instanceof Error) {\n captureReactError(\n resolveKernel(kernelSource),\n error,\n info.componentStack,\n {\n type: MECHANISM_TYPE.react.recoverableError,\n handled: true,\n synthetic: true,\n }\n );\n }\n callbacks.onRecoverableError?.(error, info);\n },\n };\n}\n"],"mappings":"2HA4DA,SAAS,cACP,OACe,CAIf,OAHK,OAGE,OAAO,QAAW,WAAa,OAAO,EAAI,OAFxC,IAGX,CA2BA,SAAgB,kBACd,QAAoC,CAAC,EACb,CACxB,GAAM,CAAE,OAAQ,aAAc,GAAG,WAAc,QAC/C,MAAO,CACL,cAAc,MAAO,KAAM,CACrB,iBAAiB,OACnB,kBACE,cAAc,YAAY,EAC1B,MACA,KAAK,eACL,CACE,KAAM,eAAe,MAAM,YAC3B,QAAS,EACX,CACF,EAEF,UAAU,gBAAgB,MAAO,IAAI,CACvC,EACA,gBAAgB,MAAO,KAAM,CACvB,iBAAiB,OACnB,kBACE,cAAc,YAAY,EAC1B,MACA,KAAK,eACL,CACE,KAAM,eAAe,MAAM,cAC3B,QAAS,EACX,CACF,EAEF,UAAU,kBAAkB,MAAO,IAAI,CACzC,EACA,mBAAmB,MAAO,KAAM,CAC1B,iBAAiB,OACnB,kBACE,cAAc,YAAY,EAC1B,MACA,KAAK,eACL,CACE,KAAM,eAAe,MAAM,iBAC3B,QAAS,GACT,UAAW,EACb,CACF,EAEF,UAAU,qBAAqB,MAAO,IAAI,CAC5C,CACF,CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.mts","names":[],"sources":["../../src/tracking/api.ts"],"mappings":";;;;;;;UAgBiB,qBAAA;EACf,MAAA,GAAS,aAAA;EACT,OAAA,UAAiB,UAAA,CAAW,KAAA;EAC5B,GAAA,GAAM,WAAA;EACN,MAAA,EAAQ,YAAA;AAAA;AAAA,cAGG,cAAA;EAAA,iBACM,MAAA;EAAA,iBACA,OAAA;EAAA,iBACA,MAAA;EAAA,iBACA,GAAA;EAAA,QAET,GAAA;EAAA,QACA,eAAA;EAAA,QACA,mBAAA;EAAA,QACA,eAAA;EAAA,QACA,aAAA;EAAA,QACA,UAAA;cAEI,IAAA,EAAM,qBAAA;EAOlB,KAAA;EASA,SAAA,IAAa,SAAA;EAQb,QAAA;EAIA,WAAA;EAIA,SAAA;EA7CyB;;;;;EAsDzB,OAAA,IAAW,MAAA;EAiBX,WAAA,IAAe,cAAA;EAIT,QAAA,CAAS,MAAA,EAAQ,cAAA,GAAiB,OAAA;EA2CxC,aAAA;EAKA,OAAA;EAAA,QAQQ,cAAA;EAAA,QAOA,YAAA;EAAA,QAcA,WAAA;EAAA,QA0BM,QAAA;AAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api.mjs","names":[],"sources":["../../src/tracking/api.ts"],"sourcesContent":["import type { SessionId } from \"@interfere/types/data/session\";\nimport type { IdentifyParams } from \"@interfere/types/sdk/identify\";\n\nimport {\n appendPathBeforeQuery,\n type IngestTarget,\n} from \"../internal/config.js\";\nimport { createLogger } from \"../util/log.js\";\nimport { DeviceManager } from \"./device.js\";\nimport { GeoDetector } from \"./geo.js\";\nimport { SessionManager } from \"./session.js\";\n\nconst log = createLogger(\"tracking\");\n\nconst SYNC_COOLDOWN_MS = 5000;\n\nexport interface SessionTrackerOptions {\n device?: DeviceManager;\n fetcher?: typeof globalThis.fetch;\n geo?: GeoDetector;\n target: IngestTarget;\n}\n\nexport class SessionTracker {\n private readonly target: IngestTarget;\n private readonly fetcher: typeof globalThis.fetch;\n private readonly device: DeviceManager;\n private readonly geo: GeoDetector;\n\n private mgr: SessionManager | null = null;\n private currentIdentity: IdentifyParams | null = null;\n private identifiedSessionId: string | null = null;\n private syncedSessionId: string | null = null;\n private syncAttemptMs = 0;\n private generation = 0;\n\n constructor(opts: SessionTrackerOptions) {\n this.target = opts.target;\n this.fetcher = opts.fetcher ?? globalThis.fetch.bind(globalThis);\n this.device = opts.device ?? new DeviceManager();\n this.geo = opts.geo ?? new GeoDetector();\n }\n\n start(): void {\n this.device.init();\n\n this.geo.detect();\n this.mgr = new SessionManager((id) => {\n this.onRotate(id).catch(() => undefined);\n });\n }\n\n sessionId(): SessionId | null {\n const id = this.mgr?.getSessionId() ?? null;\n if (id) {\n this.ensureSynced(id);\n }\n return id;\n }\n\n windowId(): string | null {\n return this.mgr?.getWindowId() ?? null;\n }\n\n getDeviceId(): string | null {\n return this.device.getDeviceId();\n }\n\n getFpHash(): string | null {\n return this.device.getFpHash();\n }\n\n /**\n * Identity headers (session / window / device). Layered onto the\n * session/identify POSTs alongside the target's static headers\n * (content-type + auth + force-enable).\n */\n headers(): Record<string, string> {\n const h: Record<string, string> = {};\n const sid = this.mgr?.getSessionId() ?? null;\n if (sid) {\n h[\"x-interfere-session\"] = sid;\n }\n const wid = this.mgr?.getWindowId() ?? null;\n if (wid) {\n h[\"x-interfere-window\"] = wid;\n }\n const did = this.device.getDeviceId();\n if (did) {\n h[\"x-interfere-device\"] = did;\n }\n return h;\n }\n\n getIdentity(): IdentifyParams | null {\n return this.currentIdentity;\n }\n\n async identify(params: IdentifyParams): Promise<void> {\n if (!this.mgr) {\n return;\n }\n const sessionId = this.mgr.getSessionId();\n if (this.identifiedSessionId === sessionId) {\n log.debug(\"skipped, already identified for session %s\", sessionId);\n return;\n }\n\n this.currentIdentity = params;\n this.identifiedSessionId = sessionId;\n\n const gen = this.generation;\n const [fpHash, country] = await Promise.all([\n this.device.whenFingerprintReady(),\n this.geo.detect(),\n ]);\n const deviceId = this.device.getDeviceId();\n if (gen !== this.generation) {\n this.identifiedSessionId = null;\n return;\n }\n\n log.info(\"POST session %s → identify\", sessionId);\n this.fetcher(appendPathBeforeQuery(this.target.url, \"/identify\"), {\n method: \"POST\",\n headers: this.requestHeaders(),\n body: JSON.stringify({\n sessionId,\n deviceId,\n fpHash,\n ...params,\n ...(country && { country }),\n }),\n keepalive: true,\n signal: AbortSignal.timeout(10_000),\n }).catch(() => {\n this.identifiedSessionId = null;\n log.warn(\"identify failed for session %s\", sessionId);\n });\n }\n\n clearIdentity(): void {\n this.currentIdentity = null;\n this.identifiedSessionId = null;\n }\n\n dispose(): void {\n this.generation += 1;\n this.clearIdentity();\n this.syncedSessionId = null;\n this.syncAttemptMs = 0;\n this.mgr = null;\n }\n\n private requestHeaders(): Record<string, string> {\n return {\n ...Object.fromEntries(this.target.headers.entries()),\n ...this.headers(),\n };\n }\n\n private ensureSynced(sessionId: string): void {\n if (this.syncedSessionId === sessionId) {\n return;\n }\n if (Date.now() - this.syncAttemptMs < SYNC_COOLDOWN_MS) {\n return;\n }\n this.syncSession(\n sessionId,\n this.device.getDeviceId(),\n this.device.getFpHash()\n );\n }\n\n private syncSession(\n sessionId: string,\n deviceId: string | null,\n fpHash: string | null\n ): void {\n this.syncAttemptMs = Date.now();\n this.syncedSessionId = sessionId;\n\n this.fetcher(this.target.url, {\n method: \"POST\",\n headers: this.requestHeaders(),\n body: JSON.stringify({ sessionId, deviceId, fpHash }),\n keepalive: true,\n signal: AbortSignal.timeout(10_000),\n })\n .then((res) => {\n if (!res.ok) {\n this.syncedSessionId = null;\n }\n })\n .catch(() => {\n this.syncedSessionId = null;\n log.warn(\"session sync failed, will retry\");\n });\n }\n\n private async onRotate(sessionId: string): Promise<void> {\n this.syncedSessionId = null;\n this.syncAttemptMs = Date.now();\n const gen = this.generation;\n const fpHash = await this.device.whenFingerprintReady();\n const deviceId = this.device.getDeviceId();\n if (gen !== this.generation) {\n return;\n }\n log.debug(\n \"POST session %s (device=%s fp=%s)\",\n sessionId,\n deviceId ?? \"pending\",\n fpHash ?? \"none\"\n );\n this.syncSession(sessionId, deviceId, fpHash);\n }\n}\n"],"mappings":"yNAYA,MAAM,IAAM,aAAa,UAAU,EAWnC,IAAa,eAAb,KAA4B,CAC1B,OACA,QACA,OACA,IAEA,IAAqC,KACrC,gBAAiD,KACjD,oBAA6C,KAC7C,gBAAyC,KACzC,cAAwB,EACxB,WAAqB,EAErB,YAAY,KAA6B,CACvC,KAAK,OAAS,KAAK,OACnB,KAAK,QAAU,KAAK,SAAW,WAAW,MAAM,KAAK,UAAU,EAC/D,KAAK,OAAS,KAAK,QAAU,IAAI,cACjC,KAAK,IAAM,KAAK,KAAO,IAAI,WAC7B,CAEA,OAAc,CACZ,KAAK,OAAO,KAAK,EAEjB,KAAK,IAAI,OAAO,EAChB,KAAK,IAAM,IAAI,eAAgB,IAAO,CACpC,KAAK,SAAS,EAAE,EAAE,UAAY,IAAA,EAAS,CACzC,CAAC,CACH,CAEA,WAA8B,CAC5B,IAAM,GAAK,KAAK,KAAK,aAAa,GAAK,KAIvC,OAHI,IACF,KAAK,aAAa,EAAE,EAEf,EACT,CAEA,UAA0B,CACxB,OAAO,KAAK,KAAK,YAAY,GAAK,IACpC,CAEA,aAA6B,CAC3B,OAAO,KAAK,OAAO,YAAY,CACjC,CAEA,WAA2B,CACzB,OAAO,KAAK,OAAO,UAAU,CAC/B,CAOA,SAAkC,CAChC,IAAM,EAA4B,CAAC,EAC7B,IAAM,KAAK,KAAK,aAAa,GAAK,KACpC,MACF,EAAE,uBAAyB,KAE7B,IAAM,IAAM,KAAK,KAAK,YAAY,GAAK,KACnC,MACF,EAAE,sBAAwB,KAE5B,IAAM,IAAM,KAAK,OAAO,YAAY,EAIpC,OAHI,MACF,EAAE,sBAAwB,KAErB,CACT,CAEA,aAAqC,CACnC,OAAO,KAAK,eACd,CAEA,MAAM,SAAS,OAAuC,CACpD,GAAI,CAAC,KAAK,IACR,OAEF,IAAM,UAAY,KAAK,IAAI,aAAa,EACxC,GAAI,KAAK,sBAAwB,UAAW,CAC1C,IAAI,MAAM,6CAA8C,SAAS,EACjE,MACF,CAEA,KAAK,gBAAkB,OACvB,KAAK,oBAAsB,UAE3B,IAAM,IAAM,KAAK,WACX,CAAC,OAAQ,SAAW,MAAM,QAAQ,IAAI,CAC1C,KAAK,OAAO,qBAAqB,EACjC,KAAK,IAAI,OAAO,CAClB,CAAC,EACK,SAAW,KAAK,OAAO,YAAY,EACzC,GAAI,MAAQ,KAAK,WAAY,CAC3B,KAAK,oBAAsB,KAC3B,MACF,CAEA,IAAI,KAAK,6BAA8B,SAAS,EAChD,KAAK,QAAQ,sBAAsB,KAAK,OAAO,IAAK,WAAW,EAAG,CAChE,OAAQ,OACR,QAAS,KAAK,eAAe,EAC7B,KAAM,KAAK,UAAU,CACnB,UACA,SACA,OACA,GAAG,OACH,GAAI,SAAW,CAAE,OAAQ,CAC3B,CAAC,EACD,UAAW,GACX,OAAQ,YAAY,QAAQ,GAAM,CACpC,CAAC,EAAE,UAAY,CACb,KAAK,oBAAsB,KAC3B,IAAI,KAAK,iCAAkC,SAAS,CACtD,CAAC,CACH,CAEA,eAAsB,CACpB,KAAK,gBAAkB,KACvB,KAAK,oBAAsB,IAC7B,CAEA,SAAgB,CACd,KAAK,YAAc,EACnB,KAAK,cAAc,EACnB,KAAK,gBAAkB,KACvB,KAAK,cAAgB,EACrB,KAAK,IAAM,IACb,CAEA,gBAAiD,CAC/C,MAAO,CACL,GAAG,OAAO,YAAY,KAAK,OAAO,QAAQ,QAAQ,CAAC,EACnD,GAAG,KAAK,QAAQ,CAClB,CACF,CAEA,aAAqB,UAAyB,CACxC,KAAK,kBAAoB,YAGzB,KAAK,IAAI,EAAI,KAAK,cAAgB,KAGtC,KAAK,YACH,UACA,KAAK,OAAO,YAAY,EACxB,KAAK,OAAO,UAAU,CACxB,EACF,CAEA,YACE,UACA,SACA,OACM,CACN,KAAK,cAAgB,KAAK,IAAI,EAC9B,KAAK,gBAAkB,UAEvB,KAAK,QAAQ,KAAK,OAAO,IAAK,CAC5B,OAAQ,OACR,QAAS,KAAK,eAAe,EAC7B,KAAM,KAAK,UAAU,CAAE,UAAW,SAAU,MAAO,CAAC,EACpD,UAAW,GACX,OAAQ,YAAY,QAAQ,GAAM,CACpC,CAAC,EACE,KAAM,KAAQ,CACR,IAAI,KACP,KAAK,gBAAkB,KAE3B,CAAC,EACA,UAAY,CACX,KAAK,gBAAkB,KACvB,IAAI,KAAK,iCAAiC,CAC5C,CAAC,CACL,CAEA,MAAc,SAAS,UAAkC,CACvD,KAAK,gBAAkB,KACvB,KAAK,cAAgB,KAAK,IAAI,EAC9B,IAAM,IAAM,KAAK,WACX,OAAS,MAAM,KAAK,OAAO,qBAAqB,EAChD,SAAW,KAAK,OAAO,YAAY,EACrC,MAAQ,KAAK,aAGjB,IAAI,MACF,oCACA,UACA,UAAY,UACZ,QAAU,MACZ,EACA,KAAK,YAAY,UAAW,SAAU,MAAM,EAC9C,CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"device.d.mts","names":[],"sources":["../../src/tracking/device.ts"],"mappings":";;AAgBA;;;;AAA+C;AAW/C;;KAXY,mBAAA,SAA4B,OAAO;;AAWZ;AAuEnC;;;;;iBAvEgB,QAAA,IAAY,OAAO;AAAA,cAuEtB,aAAA;EAAA,QACH,QAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,iBACS,mBAAA;cAEL,mBAAA,GAAsB,mBAAA;EAKlC,IAAA;EAAA,QAkCQ,gBAAA;EAgBR,WAAA;EAIA,SAAA;EAIA,oBAAA,IAAwB,OAAO;AAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"device.mjs","names":[],"sources":["../../src/tracking/device.ts"],"sourcesContent":["import { createLogger } from \"../util/log.js\";\n\nconst log = createLogger(\"device\");\n\nconst LS_KEY = \"interfere:device_id\";\nconst COOKIE_NAME = \"interfere_did\";\nconst COOKIE_MAX_AGE_DAYS = 400;\n\n/**\n * Producer of an opaque, stable per-browser fingerprint hash. The default\n * dynamic-imports the FingerprintJS OSS library and self-defers via\n * `requestIdleCallback` so the ~1.5s of synchronous font / canvas / WebGL\n * probing the library performs on a cold load doesn't land on the\n * hydration tick. Tests inject a fixed-value provider that resolves\n * immediately — they pay nothing for the production deferral.\n */\nexport type FingerprintProvider = () => Promise<string | null>;\n\nconst FP_IDLE_TIMEOUT_MS = 5000;\n\n/**\n * Resolves on the next browser idle frame, capped at 5s. Falls back to\n * `setTimeout(0)` (next macrotask) on hosts without\n * `requestIdleCallback`. Exported only for the unit test that pins the\n * \"default provider gates fingerprint work via RIC\" contract — not part\n * of the package's public surface.\n */\nexport function whenIdle(): Promise<void> {\n return new Promise((resolve) => {\n if (typeof globalThis.requestIdleCallback === \"function\") {\n globalThis.requestIdleCallback(() => resolve(), {\n timeout: FP_IDLE_TIMEOUT_MS,\n });\n return;\n }\n setTimeout(resolve, 0);\n });\n}\n\nconst defaultFingerprintProvider: FingerprintProvider = async () => {\n await whenIdle();\n try {\n const FingerprintJS = await import(\"@fingerprintjs/fingerprintjs\");\n const fp = await FingerprintJS.load();\n const result = await fp.get();\n if (!result.visitorId) {\n log.error(\n \"Fingerprinting returned an empty visitor id; visitor identity will be cookie-only\"\n );\n\n return null;\n }\n return result.visitorId;\n } catch (err) {\n log.error(\n \"fingerprint init failed; visitor identity will be cookie-only\",\n err\n );\n\n return null;\n }\n};\n\nfunction tryLocalStorage(): Storage | null {\n try {\n const s = globalThis.localStorage;\n const key = \"__interfere_device_probe__\";\n s.setItem(key, \"1\");\n s.removeItem(key);\n return s;\n } catch {\n return null;\n }\n}\n\nfunction getCookie(name: string): string | null {\n if (typeof document === \"undefined\") {\n return null;\n }\n const match = document.cookie\n .split(\"; \")\n .find((c) => c.startsWith(`${name}=`));\n return match ? decodeURIComponent(match.split(\"=\")[1] ?? \"\") : null;\n}\n\nfunction setCookie(name: string, value: string): void {\n if (typeof document === \"undefined\") {\n return;\n }\n const maxAge = COOKIE_MAX_AGE_DAYS * 24 * 60 * 60;\n // biome-ignore lint/suspicious/noDocumentCookie: Cookie Store API is async and not universally supported; synchronous access is required here\n document.cookie = `${name}=${encodeURIComponent(value)};path=/;max-age=${maxAge};SameSite=Lax`;\n}\n\nfunction generateId(): string {\n return crypto.randomUUID();\n}\n\nexport class DeviceManager {\n private deviceId: string | null = null;\n private fpHash: string | null = null;\n private fpPending: Promise<string | null> | null = null;\n private readonly fingerprintProvider: FingerprintProvider;\n\n constructor(fingerprintProvider?: FingerprintProvider) {\n this.fingerprintProvider =\n fingerprintProvider ?? defaultFingerprintProvider;\n }\n\n init(): void {\n if (this.deviceId) {\n return;\n }\n\n const ls = tryLocalStorage();\n const fromLs = ls?.getItem(LS_KEY) ?? null;\n const fromCookie = getCookie(COOKIE_NAME);\n\n this.deviceId = fromLs ?? fromCookie ?? generateId();\n\n if (!fromLs && ls) {\n ls.setItem(LS_KEY, this.deviceId);\n }\n if (!fromCookie) {\n setCookie(COOKIE_NAME, this.deviceId);\n }\n if (fromLs && !fromCookie) {\n setCookie(COOKIE_NAME, this.deviceId);\n }\n if (fromCookie && !fromLs && ls) {\n ls.setItem(LS_KEY, this.deviceId);\n }\n\n log.debug(\n \"device %s (ls=%s cookie=%s)\",\n this.deviceId,\n !!fromLs,\n !!fromCookie\n );\n\n this.startFingerprint();\n }\n\n private startFingerprint(): void {\n if (this.fpHash || this.fpPending) {\n return;\n }\n\n this.fpPending = (async () => {\n const hash = await this.fingerprintProvider();\n this.fpHash = hash;\n if (hash) {\n log.debug(\"fpHash %s\", hash);\n }\n\n return hash;\n })();\n }\n\n getDeviceId(): string | null {\n return this.deviceId;\n }\n\n getFpHash(): string | null {\n return this.fpHash;\n }\n\n whenFingerprintReady(): Promise<string | null> {\n if (this.fpHash) {\n return Promise.resolve(this.fpHash);\n }\n return this.fpPending ?? Promise.resolve(null);\n }\n}\n"],"mappings":"0CAEA,MAAM,IAAM,aAAa,QAAQ,EAE3B,OAAS,sBACT,YAAc,gBAsBpB,SAAgB,UAA0B,CACxC,OAAO,IAAI,QAAS,SAAY,CAC9B,GAAI,OAAO,WAAW,qBAAwB,WAAY,CACxD,WAAW,wBAA0B,QAAQ,EAAG,CAC9C,QAAS,GACX,CAAC,EACD,MACF,CACA,WAAW,QAAS,CAAC,CACvB,CAAC,CACH,CAEA,MAAM,2BAAkD,SAAY,CAClE,MAAM,SAAS,EACf,GAAI,CAGF,IAAM,OAAS,MAAM,MADJ,MADW,OAAO,iCACJ,KAAK,GACZ,IAAI,EAQ5B,OAPK,OAAO,UAOL,OAAO,WANZ,IAAI,MACF,mFACF,EAEO,KAGX,OAAS,IAAK,CAMZ,OALA,IAAI,MACF,gEACA,GACF,EAEO,IACT,CACF,EAEA,SAAS,iBAAkC,CACzC,GAAI,CACF,IAAM,EAAI,WAAW,aACf,IAAM,6BAGZ,OAFA,EAAE,QAAQ,IAAK,GAAG,EAClB,EAAE,WAAW,GAAG,EACT,CACT,MAAQ,CACN,OAAO,IACT,CACF,CAEA,SAAS,UAAU,KAA6B,CAC9C,GAAI,OAAO,SAAa,IACtB,OAAO,KAET,IAAM,MAAQ,SAAS,OACpB,MAAM,IAAI,EACV,KAAM,GAAM,EAAE,WAAW,GAAG,KAAK,EAAE,CAAC,EACvC,OAAO,MAAQ,mBAAmB,MAAM,MAAM,GAAG,EAAE,IAAM,EAAE,EAAI,IACjE,CAEA,SAAS,UAAU,KAAc,MAAqB,CAChD,OAAO,SAAa,MAKxB,SAAS,OAAS,GAAG,KAAK,GAAG,mBAAmB,KAAK,EAAE,kBAFxC,IAAsB,GAAK,GAAK,GAEiC,eAClF,CAEA,SAAS,YAAqB,CAC5B,OAAO,OAAO,WAAW,CAC3B,CAEA,IAAa,cAAb,KAA2B,CACzB,SAAkC,KAClC,OAAgC,KAChC,UAAmD,KACnD,oBAEA,YAAY,oBAA2C,CACrD,KAAK,oBACH,qBAAuB,0BAC3B,CAEA,MAAa,CACX,GAAI,KAAK,SACP,OAGF,IAAM,GAAK,gBAAgB,EACrB,OAAS,IAAI,QAAQ,MAAM,GAAK,KAChC,WAAa,UAAU,WAAW,EAExC,KAAK,SAAW,QAAU,YAAc,WAAW,EAE/C,CAAC,QAAU,IACb,GAAG,QAAQ,OAAQ,KAAK,QAAQ,EAE7B,YACH,UAAU,YAAa,KAAK,QAAQ,EAElC,QAAU,CAAC,YACb,UAAU,YAAa,KAAK,QAAQ,EAElC,YAAc,CAAC,QAAU,IAC3B,GAAG,QAAQ,OAAQ,KAAK,QAAQ,EAGlC,IAAI,MACF,8BACA,KAAK,SACL,CAAC,CAAC,OACF,CAAC,CAAC,UACJ,EAEA,KAAK,iBAAiB,CACxB,CAEA,kBAAiC,CAC3B,KAAK,QAAU,KAAK,YAIxB,KAAK,WAAa,SAAY,CAC5B,IAAM,KAAO,MAAM,KAAK,oBAAoB,EAM5C,MALA,MAAK,OAAS,KACV,MACF,IAAI,MAAM,YAAa,IAAI,EAGtB,IACT,GAAG,EACL,CAEA,aAA6B,CAC3B,OAAO,KAAK,QACd,CAEA,WAA2B,CACzB,OAAO,KAAK,MACd,CAEA,sBAA+C,CAI7C,OAHI,KAAK,OACA,QAAQ,QAAQ,KAAK,MAAM,EAE7B,KAAK,WAAa,QAAQ,QAAQ,IAAI,CAC/C,CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"geo.d.mts","names":[],"sources":["../../src/tracking/geo.ts"],"mappings":";cAea,WAAA;EAAA,QACH,MAAA;EAAA,QACA,OAAA;EAAA,QACA,MAAA;EAAA,iBACS,OAAA;cAEL,OAAA,UAAiB,UAAA,CAAW,KAAA;EAIxC,MAAA,IAAU,OAAO;EAuBjB,UAAA;EAAA,QAIc,gBAAA;AAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"geo.mjs","names":[],"sources":["../../src/tracking/geo.ts"],"sourcesContent":["import { cloudflareTraceSchema } from \"@interfere/types/sdk/geo\";\n\nconst CF_TRACE_URL = \"https://cloudflare.com/cdn-cgi/trace\";\n\nfunction parseTrace(text: string): Record<string, string> {\n const result: Record<string, string> = {};\n for (const line of text.split(\"\\n\")) {\n const idx = line.indexOf(\"=\");\n if (idx > 0) {\n result[line.slice(0, idx)] = line.slice(idx + 1);\n }\n }\n return result;\n}\n\nexport class GeoDetector {\n private cached: string | null = null;\n private pending: Promise<string | null> | null = null;\n private failed = false;\n private readonly fetcher: typeof globalThis.fetch;\n\n constructor(fetcher?: typeof globalThis.fetch) {\n this.fetcher = fetcher ?? globalThis.fetch.bind(globalThis);\n }\n\n detect(): Promise<string | null> {\n if (this.cached) {\n return Promise.resolve(this.cached);\n }\n\n if (this.failed) {\n return Promise.resolve(null);\n }\n\n if (!this.pending) {\n this.pending = this.fetchCountryCode().then((code) => {\n this.cached = code;\n if (!code) {\n this.failed = true;\n }\n this.pending = null;\n return code;\n });\n }\n\n return this.pending;\n }\n\n getCountry(): string | null {\n return this.cached;\n }\n\n private async fetchCountryCode(): Promise<string | null> {\n try {\n const res = await this.fetcher(CF_TRACE_URL, {\n signal: AbortSignal.timeout(3000),\n });\n if (!res.ok) {\n return null;\n }\n\n const raw = parseTrace(await res.text());\n const trace = cloudflareTraceSchema.safeParse(raw);\n if (!trace.success) {\n return null;\n }\n\n return trace.data.loc.toUpperCase();\n } catch {\n return null;\n }\n }\n}\n"],"mappings":"4DAIA,SAAS,WAAW,KAAsC,CACxD,IAAM,OAAiC,CAAC,EACxC,IAAK,IAAM,QAAQ,KAAK,MAAM;CAAI,EAAG,CACnC,IAAM,IAAM,KAAK,QAAQ,GAAG,EACxB,IAAM,IACR,OAAO,KAAK,MAAM,EAAG,GAAG,GAAK,KAAK,MAAM,IAAM,CAAC,EAEnD,CACA,OAAO,MACT,CAEA,IAAa,YAAb,KAAyB,CACvB,OAAgC,KAChC,QAAiD,KACjD,OAAiB,GACjB,QAEA,YAAY,QAAmC,CAC7C,KAAK,QAAU,SAAW,WAAW,MAAM,KAAK,UAAU,CAC5D,CAEA,QAAiC,CAoB/B,OAnBI,KAAK,OACA,QAAQ,QAAQ,KAAK,MAAM,EAGhC,KAAK,OACA,QAAQ,QAAQ,IAAI,GAG7B,AACE,KAAK,UAAU,KAAK,iBAAiB,EAAE,KAAM,OAC3C,KAAK,OAAS,KACT,OACH,KAAK,OAAS,IAEhB,KAAK,QAAU,KACR,KACR,EAGI,KAAK,QACd,CAEA,YAA4B,CAC1B,OAAO,KAAK,MACd,CAEA,MAAc,kBAA2C,CACvD,GAAI,CACF,IAAM,IAAM,MAAM,KAAK,QAAQ,uCAAc,CAC3C,OAAQ,YAAY,QAAQ,GAAI,CAClC,CAAC,EACD,GAAI,CAAC,IAAI,GACP,OAAO,KAGT,IAAM,IAAM,WAAW,MAAM,IAAI,KAAK,CAAC,EACjC,MAAQ,sBAAsB,UAAU,GAAG,EAKjD,OAJK,MAAM,QAIJ,MAAM,KAAK,IAAI,YAAY,EAHzB,IAIX,MAAQ,CACN,OAAO,IACT,CACF,CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.mts","names":[],"sources":["../../src/tracking/session.ts"],"mappings":";;;KAqBY,QAAA,IAAY,SAAiB;AAAA,cAE5B,cAAA;EACX,SAAA;EACA,QAAA;EAAA,iBACiB,KAAA;EAAA,iBACA,OAAA;EAAA,iBACA,QAAA;EAAA,QACT,cAAA;cAEI,QAAA,GAAW,QAAA;EAOvB,YAAA,IAAgB,SAAS;EAQzB,WAAA;EAAA,QAgBQ,OAAA;EAAA,QAWA,MAAA;EAAA,QAQA,KAAA;EAAA,QAKA,SAAA;AAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"session.mjs","names":["uuidv7"],"sources":["../../src/tracking/session.ts"],"sourcesContent":["import type { SessionId } from \"@interfere/types/data/session\";\n\nimport { v7 as uuidv7 } from \"uuid\";\n\nconst SESSION_ID_KEY = \"interfere:session_id\";\nconst LAST_ACTIVITY_KEY = \"interfere:last_activity\";\nconst WINDOW_ID_KEY = \"interfere:window_id\";\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000;\n\nfunction tryStorage(type: \"localStorage\" | \"sessionStorage\"): Storage | null {\n try {\n const s = globalThis[type];\n const key = \"__interfere_probe__\";\n s.setItem(key, \"1\");\n s.removeItem(key);\n return s;\n } catch {\n return null;\n }\n}\n\nexport type OnRotate = (sessionId: string) => void;\n\nexport class SessionManager {\n sessionId: string | null = null;\n windowId: string | null = null;\n private readonly local: Storage | null;\n private readonly session: Storage | null;\n private readonly onRotate: OnRotate | null = null;\n private lastActivityMs = 0;\n\n constructor(onRotate?: OnRotate) {\n this.local = tryStorage(\"localStorage\");\n this.session = tryStorage(\"sessionStorage\");\n this.onRotate = onRotate ?? null;\n this.restore();\n }\n\n getSessionId(): SessionId {\n if (this.sessionId && !this.isExpired()) {\n this.touch();\n return this.sessionId as SessionId;\n }\n return this.rotate() as SessionId;\n }\n\n getWindowId(): string {\n if (this.windowId) {\n return this.windowId;\n }\n\n const stored = this.session?.getItem(WINDOW_ID_KEY);\n if (stored) {\n this.windowId = stored;\n return stored;\n }\n\n this.windowId = `win_${crypto.randomUUID()}`;\n this.session?.setItem(WINDOW_ID_KEY, this.windowId);\n return this.windowId;\n }\n\n private restore(): void {\n const stored = this.local?.getItem(SESSION_ID_KEY);\n\n if (stored && !this.isExpired()) {\n this.sessionId = stored;\n this.touch();\n } else {\n this.rotate();\n }\n }\n\n private rotate(): SessionId {\n this.sessionId = uuidv7();\n this.local?.setItem(SESSION_ID_KEY, this.sessionId);\n this.touch();\n this.onRotate?.(this.sessionId);\n return this.sessionId as SessionId;\n }\n\n private touch(): void {\n this.lastActivityMs = Date.now();\n this.local?.setItem(LAST_ACTIVITY_KEY, String(this.lastActivityMs));\n }\n\n private isExpired(): boolean {\n const raw = this.local?.getItem(LAST_ACTIVITY_KEY);\n const ts = raw ? Number(raw) : this.lastActivityMs;\n\n if (ts === 0) {\n return true;\n }\n return Date.now() - ts > SESSION_TIMEOUT_MS;\n }\n}\n"],"mappings":"qBAIA,MAAM,eAAiB,uBACjB,kBAAoB,0BACpB,cAAgB,sBAGtB,SAAS,WAAW,KAAyD,CAC3E,GAAI,CACF,IAAM,EAAI,WAAW,MACf,IAAM,sBAGZ,OAFA,EAAE,QAAQ,IAAK,GAAG,EAClB,EAAE,WAAW,GAAG,EACT,CACT,MAAQ,CACN,OAAO,IACT,CACF,CAIA,IAAa,eAAb,KAA4B,CAC1B,UAA2B,KAC3B,SAA0B,KAC1B,MACA,QACA,SAA6C,KAC7C,eAAyB,EAEzB,YAAY,SAAqB,CAC/B,KAAK,MAAQ,WAAW,cAAc,EACtC,KAAK,QAAU,WAAW,gBAAgB,EAC1C,KAAK,SAAW,UAAY,KAC5B,KAAK,QAAQ,CACf,CAEA,cAA0B,CAKxB,OAJI,KAAK,WAAa,CAAC,KAAK,UAAU,GACpC,KAAK,MAAM,EACJ,KAAK,WAEP,KAAK,OAAO,CACrB,CAEA,aAAsB,CACpB,GAAI,KAAK,SACP,OAAO,KAAK,SAGd,IAAM,OAAS,KAAK,SAAS,QAAQ,aAAa,EAQlD,OAPI,QACF,KAAK,SAAW,OACT,SAGT,KAAK,SAAW,OAAO,OAAO,WAAW,IACzC,KAAK,SAAS,QAAQ,cAAe,KAAK,QAAQ,EAC3C,KAAK,SACd,CAEA,SAAwB,CACtB,IAAM,OAAS,KAAK,OAAO,QAAQ,cAAc,EAE7C,QAAU,CAAC,KAAK,UAAU,GAC5B,KAAK,UAAY,OACjB,KAAK,MAAM,GAEX,KAAK,OAAO,CAEhB,CAEA,QAA4B,CAK1B,MAJA,MAAK,UAAYA,GAAO,EACxB,KAAK,OAAO,QAAQ,eAAgB,KAAK,SAAS,EAClD,KAAK,MAAM,EACX,KAAK,WAAW,KAAK,SAAS,EACvB,KAAK,SACd,CAEA,OAAsB,CACpB,KAAK,eAAiB,KAAK,IAAI,EAC/B,KAAK,OAAO,QAAQ,kBAAmB,OAAO,KAAK,cAAc,CAAC,CACpE,CAEA,WAA6B,CAC3B,IAAM,IAAM,KAAK,OAAO,QAAQ,iBAAiB,EAC3C,GAAK,IAAM,OAAO,GAAG,EAAI,KAAK,eAKpC,OAHI,KAAO,EACF,GAEF,KAAK,IAAI,EAAI,GAAK,IAC3B,CACF"}
|
package/dist/util/bot.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bot.d.mts","names":[],"sources":["../../src/util/bot.ts"],"mappings":";;AAQA;;;;AAA8B;iBAAd,cAAA"}
|
package/dist/util/bot.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"bot.mjs","names":[],"sources":["../../src/util/bot.ts"],"sourcesContent":["import { isBot } from \"@ua-parser-js/pro-enterprise/bot-detection\";\n\n/**\n * `isBot` covers the union of the Crawlers, Fetchers, CLIs, and Libraries\n * extensions, so `isAICrawler` / `isAIAssistant` are strict subsets that\n * don't need separate checks. Returns `false` outside the browser (no UA\n * to inspect).\n */\nexport function isBotUserAgent(): boolean {\n if (typeof navigator === \"undefined\") {\n return false;\n }\n return isBot(navigator.userAgent);\n}\n"],"mappings":"8DAQA,SAAgB,gBAA0B,CAIxC,OAHI,OAAO,UAAc,IAChB,GAEF,MAAM,UAAU,SAAS,CAClC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"global.d.mts","names":[],"sources":["../../src/util/global.ts"],"mappings":";;AAMA;;;;;iBAAgB,SAAA,IAAa,GAAA,WAAc,CAAC"}
|
package/dist/util/global.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"global.mjs","names":[],"sources":["../../src/util/global.ts"],"sourcesContent":["/**\n * Typed read of a build-time-injected global. The Vite plugin and\n * `withInterfere()` stamp values like `__INTERFERE_RELEASE_SLUG__` onto\n * `globalThis`; this is the single read site so the unsafe cast lives in\n * one place.\n */\nexport function getGlobal<T>(key: string): T | undefined {\n return (globalThis as Record<string, unknown>)[key] as T | undefined;\n}\n"],"mappings":"AAMA,SAAgB,UAAa,IAA4B,CACvD,OAAQ,WAAuC,IACjD"}
|
package/dist/util/log.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"log.d.mts","names":[],"sources":["../../src/util/log.ts"],"mappings":";KAAY,QAAA;AAAA,iBA6BI,WAAA,CAAY,KAAe,EAAR,QAAQ;AAAA,iBAI3B,WAAA,IAAe,QAAQ;AAAA,UAKtB,MAAA;EACf,KAAA,IAAS,IAAA;EACT,KAAA,IAAS,IAAA;EACT,IAAA,IAAQ,IAAA;EACR,IAAA,IAAQ,IAAA;AAAA;AAAA,iBAeM,YAAA,CAAa,KAAA,WAAgB,MAAM"}
|
package/dist/util/log.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"log.mjs","names":[],"sources":["../../src/util/log.ts"],"sourcesContent":["export type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\" | \"none\";\n\nconst PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n none: 4,\n};\n\nconst CONSOLE_FN: Record<\n Exclude<LogLevel, \"none\">,\n \"debug\" | \"info\" | \"warn\" | \"error\"\n> = {\n debug: \"debug\",\n info: \"info\",\n warn: \"warn\",\n error: \"error\",\n};\n\n/**\n * Customer apps see `warn` and above by default. Tests opt into silence by\n * calling `setLogLevel(\"none\")` in their setup file (we don't auto-detect\n * the test runtime — verified empirically that vitest browser mode exposes\n * neither `import.meta.env`, `process.env`, nor `__VITEST_*` globals to\n * library code, so any \"is this a test?\" probe is unreliable).\n */\nlet threshold = PRIORITY.warn;\n\nexport function setLogLevel(level: LogLevel): void {\n threshold = PRIORITY[level];\n}\n\nexport function getLogLevel(): LogLevel {\n const entry = Object.entries(PRIORITY).find(([, v]) => v === threshold);\n return (entry?.[0] ?? \"warn\") as LogLevel;\n}\n\nexport interface Logger {\n debug(...args: unknown[]): void;\n error(...args: unknown[]): void;\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n}\n\nfunction emit(\n level: Exclude<LogLevel, \"none\">,\n prefix: string,\n args: unknown[]\n): void {\n if (PRIORITY[level] < threshold) {\n return;\n }\n const fn = CONSOLE_FN[level];\n globalThis.console[fn](prefix, ...args);\n}\n\nexport function createLogger(scope: string): Logger {\n const prefix = `[Interfere:${scope}]`;\n return {\n debug: (...args) => emit(\"debug\", prefix, args),\n info: (...args) => emit(\"info\", prefix, args),\n warn: (...args) => emit(\"warn\", prefix, args),\n error: (...args) => emit(\"error\", prefix, args),\n };\n}\n"],"mappings":"AAEA,MAAM,SAAqC,CACzC,MAAO,EACP,KAAM,EACN,KAAM,EACN,MAAO,EACP,KAAM,CACR,EAEM,WAGF,CACF,MAAO,QACP,KAAM,OACN,KAAM,OACN,MAAO,OACT,EASA,IAAI,UAAY,SAAS,KAEzB,SAAgB,YAAY,MAAuB,CACjD,UAAY,SAAS,MACvB,CAEA,SAAgB,aAAwB,CAEtC,OADc,OAAO,QAAQ,QAAQ,EAAE,MAAM,EAAG,KAAO,IAAM,SACjD,IAAI,IAAM,MACxB,CASA,SAAS,KACP,MACA,OACA,KACM,CACN,GAAI,SAAS,OAAS,UACpB,OAEF,IAAM,GAAK,WAAW,OACtB,WAAW,QAAQ,IAAI,OAAQ,GAAG,IAAI,CACxC,CAEA,SAAgB,aAAa,MAAuB,CAClD,IAAM,OAAS,cAAc,MAAM,GACnC,MAAO,CACL,OAAQ,GAAG,OAAS,KAAK,QAAS,OAAQ,IAAI,EAC9C,MAAO,GAAG,OAAS,KAAK,OAAQ,OAAQ,IAAI,EAC5C,MAAO,GAAG,OAAS,KAAK,OAAQ,OAAQ,IAAI,EAC5C,OAAQ,GAAG,OAAS,KAAK,QAAS,OAAQ,IAAI,CAChD,CACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stringify.d.mts","names":[],"sources":["../../src/util/stringify.ts"],"mappings":";;AAKA;;;;iBAAgB,aAAA,CAAc,KAAc"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stringify.mjs","names":[],"sources":["../../src/util/stringify.ts"],"sourcesContent":["/**\n * `JSON.stringify` with a `String(value)` fallback for circular refs and\n * non-serialisable values (BigInt, …). Strings pass through unchanged so\n * single-arg log calls don't get re-quoted.\n */\nexport function safeStringify(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n"],"mappings":"AAKA,SAAgB,cAAc,MAAwB,CACpD,GAAI,OAAO,OAAU,SACnB,OAAO,MAET,GAAI,CACF,OAAO,KAAK,UAAU,KAAK,GAAK,OAAO,KAAK,CAC9C,MAAQ,CACN,OAAO,OAAO,KAAK,CACrB,CACF"}
|