@probat/react 0.4.2 → 0.4.4

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/index.d.mts CHANGED
@@ -64,11 +64,9 @@ interface ExperimentProps {
64
64
  }
65
65
  declare function Experiment({ id, control, variants, track, componentInstanceId, fallback, debug, }: ExperimentProps): React$1.JSX.Element;
66
66
 
67
- interface UseTrackOptions {
67
+ interface UseTrackBaseOptions {
68
68
  /** Experiment identifier */
69
69
  experimentId: string;
70
- /** The variant key to attach to events */
71
- variantKey: string;
72
70
  /** Stable instance id when multiple instances of the same experiment exist on a page */
73
71
  componentInstanceId?: string;
74
72
  /** Auto-track impressions (default true) */
@@ -82,21 +80,41 @@ interface UseTrackOptions {
82
80
  /** Log events to console */
83
81
  debug?: boolean;
84
82
  }
83
+ /** Explicit mode: pass the variant key directly */
84
+ interface UseTrackExplicitOptions extends UseTrackBaseOptions {
85
+ /** The variant key to attach to events */
86
+ variantKey: string;
87
+ customerId?: undefined;
88
+ }
89
+ /** Customer mode: backend resolves variant from assignment table */
90
+ interface UseTrackCustomerOptions extends UseTrackBaseOptions {
91
+ variantKey?: undefined;
92
+ /** Customer ID to resolve variant server-side. Falls back to provider's customerId. */
93
+ customerId?: string;
94
+ }
95
+ type UseTrackOptions = UseTrackExplicitOptions | UseTrackCustomerOptions;
85
96
  /**
86
97
  * Attaches impression and click tracking to a DOM element via a ref.
87
- * Completely independent of variant assignment — pass the variantKey explicitly.
98
+ *
99
+ * Two modes:
100
+ * - **Explicit**: pass `variantKey` directly — stamped on every event.
101
+ * - **Customer**: omit `variantKey` — backend resolves it from the assignment table
102
+ * using `(experimentId, customerId)`.
88
103
  *
89
104
  * @example
90
105
  * ```tsx
106
+ * // Explicit mode
91
107
  * const trackRef = useTrack({ experimentId: "pricing", variantKey: "ai_v1" });
92
- * return <div ref={trackRef}>...</div>;
108
+ *
109
+ * // Customer mode (backend resolves variant)
110
+ * const trackRef = useTrack({ experimentId: "pricing", customerId: "user_123" });
93
111
  * ```
94
112
  */
95
113
  declare function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement | null>;
96
114
 
97
- interface TrackProps extends UseTrackOptions {
115
+ type TrackProps = UseTrackOptions & {
98
116
  children: React$1.ReactNode;
99
- }
117
+ };
100
118
  /**
101
119
  * Wrapper component that attaches impression and click tracking to its children.
102
120
  * Alternative to the `useTrack` hook when you prefer a component over a ref.
@@ -194,4 +212,4 @@ declare function createExperimentContext(experimentId: string): {
194
212
  useVariantKey: () => string;
195
213
  };
196
214
 
197
- export { type DecisionResponse, Experiment, type ExperimentProps, type ExperimentTrackOptions, type MetricPayload, ProbatProviderClient, type ProbatProviderProps, Track, type TrackProps, type UseExperimentOptions, type UseExperimentReturn, type UseProbatMetricsReturn, type UseTrackOptions, createExperimentContext, fetchDecision, sendMetric, useExperiment, useProbatMetrics, useTrack };
215
+ export { type DecisionResponse, Experiment, type ExperimentProps, type ExperimentTrackOptions, type MetricPayload, ProbatProviderClient, type ProbatProviderProps, Track, type TrackProps, type UseExperimentOptions, type UseExperimentReturn, type UseProbatMetricsReturn, type UseTrackCustomerOptions, type UseTrackExplicitOptions, type UseTrackOptions, createExperimentContext, fetchDecision, sendMetric, useExperiment, useProbatMetrics, useTrack };
package/dist/index.d.ts CHANGED
@@ -64,11 +64,9 @@ interface ExperimentProps {
64
64
  }
65
65
  declare function Experiment({ id, control, variants, track, componentInstanceId, fallback, debug, }: ExperimentProps): React$1.JSX.Element;
66
66
 
67
- interface UseTrackOptions {
67
+ interface UseTrackBaseOptions {
68
68
  /** Experiment identifier */
69
69
  experimentId: string;
70
- /** The variant key to attach to events */
71
- variantKey: string;
72
70
  /** Stable instance id when multiple instances of the same experiment exist on a page */
73
71
  componentInstanceId?: string;
74
72
  /** Auto-track impressions (default true) */
@@ -82,21 +80,41 @@ interface UseTrackOptions {
82
80
  /** Log events to console */
83
81
  debug?: boolean;
84
82
  }
83
+ /** Explicit mode: pass the variant key directly */
84
+ interface UseTrackExplicitOptions extends UseTrackBaseOptions {
85
+ /** The variant key to attach to events */
86
+ variantKey: string;
87
+ customerId?: undefined;
88
+ }
89
+ /** Customer mode: backend resolves variant from assignment table */
90
+ interface UseTrackCustomerOptions extends UseTrackBaseOptions {
91
+ variantKey?: undefined;
92
+ /** Customer ID to resolve variant server-side. Falls back to provider's customerId. */
93
+ customerId?: string;
94
+ }
95
+ type UseTrackOptions = UseTrackExplicitOptions | UseTrackCustomerOptions;
85
96
  /**
86
97
  * Attaches impression and click tracking to a DOM element via a ref.
87
- * Completely independent of variant assignment — pass the variantKey explicitly.
98
+ *
99
+ * Two modes:
100
+ * - **Explicit**: pass `variantKey` directly — stamped on every event.
101
+ * - **Customer**: omit `variantKey` — backend resolves it from the assignment table
102
+ * using `(experimentId, customerId)`.
88
103
  *
89
104
  * @example
90
105
  * ```tsx
106
+ * // Explicit mode
91
107
  * const trackRef = useTrack({ experimentId: "pricing", variantKey: "ai_v1" });
92
- * return <div ref={trackRef}>...</div>;
108
+ *
109
+ * // Customer mode (backend resolves variant)
110
+ * const trackRef = useTrack({ experimentId: "pricing", customerId: "user_123" });
93
111
  * ```
94
112
  */
95
113
  declare function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement | null>;
96
114
 
97
- interface TrackProps extends UseTrackOptions {
115
+ type TrackProps = UseTrackOptions & {
98
116
  children: React$1.ReactNode;
99
- }
117
+ };
100
118
  /**
101
119
  * Wrapper component that attaches impression and click tracking to its children.
102
120
  * Alternative to the `useTrack` hook when you prefer a component over a ref.
@@ -194,4 +212,4 @@ declare function createExperimentContext(experimentId: string): {
194
212
  useVariantKey: () => string;
195
213
  };
196
214
 
197
- export { type DecisionResponse, Experiment, type ExperimentProps, type ExperimentTrackOptions, type MetricPayload, ProbatProviderClient, type ProbatProviderProps, Track, type TrackProps, type UseExperimentOptions, type UseExperimentReturn, type UseProbatMetricsReturn, type UseTrackOptions, createExperimentContext, fetchDecision, sendMetric, useExperiment, useProbatMetrics, useTrack };
215
+ export { type DecisionResponse, Experiment, type ExperimentProps, type ExperimentTrackOptions, type MetricPayload, ProbatProviderClient, type ProbatProviderProps, Track, type TrackProps, type UseExperimentOptions, type UseExperimentReturn, type UseProbatMetricsReturn, type UseTrackCustomerOptions, type UseTrackExplicitOptions, type UseTrackOptions, createExperimentContext, fetchDecision, sendMetric, useExperiment, useProbatMetrics, useTrack };
package/dist/index.js CHANGED
@@ -364,7 +364,6 @@ var useStableInstanceId = typeof React3__default.default.useId === "function" ?
364
364
  function useTrack(options) {
365
365
  const {
366
366
  experimentId,
367
- variantKey,
368
367
  componentInstanceId,
369
368
  impression: trackImpression = true,
370
369
  click: trackClick = true,
@@ -372,25 +371,37 @@ function useTrack(options) {
372
371
  clickEventName = "$experiment_click",
373
372
  debug = false
374
373
  } = options;
375
- const { host, customerId } = useProbatContext();
374
+ const variantKey = options.variantKey ?? void 0;
375
+ const explicitCustomerId = "customerId" in options ? options.customerId : void 0;
376
+ const { host, customerId: providerCustomerId } = useProbatContext();
377
+ const resolvedCustomerId = explicitCustomerId ?? providerCustomerId;
378
+ const isCustomerMode = !variantKey;
376
379
  const autoInstanceId = useStableInstanceId(experimentId);
377
380
  const instanceId = componentInstanceId ?? autoInstanceId;
378
381
  const containerRef = React3.useRef(null);
379
382
  const impressionSent = React3.useRef(false);
383
+ React3.useEffect(() => {
384
+ if (isCustomerMode && !resolvedCustomerId && debug) {
385
+ console.warn(
386
+ `[probat] useTrack called without variantKey and no customerId available for "${experimentId}". Events will have no variant attribution.`
387
+ );
388
+ }
389
+ }, [isCustomerMode, resolvedCustomerId, experimentId, debug]);
380
390
  const eventProps = React3.useMemo(
381
391
  () => ({
382
392
  experiment_id: experimentId,
383
- variant_key: variantKey,
393
+ ...variantKey ? { variant_key: variantKey } : {},
384
394
  component_instance_id: instanceId,
385
- ...customerId ? { distinct_id: customerId } : {}
395
+ ...resolvedCustomerId ? { distinct_id: resolvedCustomerId } : {}
386
396
  }),
387
- [experimentId, variantKey, instanceId, customerId]
397
+ [experimentId, variantKey, instanceId, resolvedCustomerId]
388
398
  );
399
+ const dedupeVariant = variantKey ?? resolvedCustomerId ?? "__anon__";
389
400
  React3.useEffect(() => {
390
401
  if (!trackImpression) return;
391
402
  impressionSent.current = false;
392
403
  const pageKey = getPageKey();
393
- const dedupeKey = makeDedupeKey(experimentId, variantKey, instanceId, pageKey);
404
+ const dedupeKey = makeDedupeKey(experimentId, dedupeVariant, instanceId, pageKey);
394
405
  if (hasSeen(dedupeKey)) {
395
406
  impressionSent.current = true;
396
407
  return;
@@ -434,7 +445,7 @@ function useTrack(options) {
434
445
  }, [
435
446
  trackImpression,
436
447
  experimentId,
437
- variantKey,
448
+ dedupeVariant,
438
449
  instanceId,
439
450
  host,
440
451
  impressionEventName,
@@ -519,7 +530,7 @@ function Track({ children, ...trackOptions }) {
519
530
  {
520
531
  ref: trackRef,
521
532
  "data-probat-track": trackOptions.experimentId,
522
- "data-probat-variant": trackOptions.variantKey,
533
+ "data-probat-variant": trackOptions.variantKey ?? "server-resolved",
523
534
  style: { display: "contents" }
524
535
  },
525
536
  children
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context/ProbatContext.tsx","../src/components/ProbatProviderClient.tsx","../src/utils/environment.ts","../src/utils/eventContext.ts","../src/utils/api.ts","../src/hooks/useExperiment.ts","../src/utils/dedupeStorage.ts","../src/utils/stableInstanceId.ts","../src/hooks/useTrack.ts","../src/components/Experiment.tsx","../src/components/Track.tsx","../src/hooks/useProbatMetrics.ts","../src/utils/createExperimentContext.ts"],"names":["createContext","useMemo","React","useContext","useState","useEffect","useRef","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\n\nimport React, { createContext, useContext, useMemo } from \"react\";\n\nexport interface ProbatContextValue {\n host: string;\n customerId?: string;\n bootstrap: Record<string, string>;\n}\n\nconst ProbatContext = createContext<ProbatContextValue | null>(null);\n\nconst DEFAULT_HOST = \"https://gushi.onrender.com\";\n\nexport interface ProbatProviderProps {\n /** Your end-user's ID. When provided, used as the distinct_id for variant\n * assignment (consistent across devices) and attached to all events. */\n customerId?: string;\n /** Base URL for the Probat API. Defaults to https://gushi.onrender.com */\n host?: string;\n /**\n * Bootstrap assignments to avoid flash on first render.\n * Map of experiment id → variant key.\n * e.g. { \"cta-copy-test\": \"ai_v1\" }\n */\n bootstrap?: Record<string, string>;\n children: React.ReactNode;\n}\n\nexport function ProbatProvider({\n customerId,\n host = DEFAULT_HOST,\n bootstrap,\n children,\n}: ProbatProviderProps) {\n const value = useMemo<ProbatContextValue>(\n () => ({\n host: host.replace(/\\/$/, \"\"),\n customerId,\n bootstrap: bootstrap ?? {},\n }),\n [customerId, host, bootstrap]\n );\n\n return (\n <ProbatContext.Provider value={value}>\n {children}\n </ProbatContext.Provider>\n );\n}\n\nexport function useProbatContext(): ProbatContextValue {\n const ctx = useContext(ProbatContext);\n if (!ctx) {\n throw new Error(\n \"useProbatContext must be used within <ProbatProviderClient>. Wrap your app with <ProbatProviderClient>.\"\n );\n }\n return ctx;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { ProbatProvider } from \"../context/ProbatContext\";\nimport type { ProbatProviderProps } from \"../context/ProbatContext\";\n\n/**\n * Client-only provider for Next.js App Router.\n * Import this in your layout/providers file.\n *\n * @example\n * ```tsx\n * // app/providers.tsx\n * \"use client\";\n * import { ProbatProviderClient } from \"@probat/react\";\n *\n * export function Providers({ children }) {\n * return (\n * <ProbatProviderClient customerId={user.id}>\n * {children}\n * </ProbatProviderClient>\n * );\n * }\n * ```\n */\nexport function ProbatProviderClient(props: ProbatProviderProps) {\n return React.createElement(ProbatProvider, props);\n}\n\nexport type { ProbatProviderProps };\n","/**\n * Detect if the code is running on localhost (development environment).\n * Returns \"dev\" for localhost, \"prod\" for production.\n */\nexport function detectEnvironment(): \"dev\" | \"prod\" {\n if (typeof window === \"undefined\") {\n return \"prod\"; // Server-side, default to prod\n }\n\n const hostname = window.location.hostname;\n\n // Check for localhost, 127.0.0.1, or local IP addresses\n if (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"0.0.0.0\" ||\n hostname.startsWith(\"192.168.\") ||\n hostname.startsWith(\"10.\") ||\n hostname.startsWith(\"172.16.\") ||\n hostname.startsWith(\"172.17.\") ||\n hostname.startsWith(\"172.18.\") ||\n hostname.startsWith(\"172.19.\") ||\n hostname.startsWith(\"172.20.\") ||\n hostname.startsWith(\"172.21.\") ||\n hostname.startsWith(\"172.22.\") ||\n hostname.startsWith(\"172.23.\") ||\n hostname.startsWith(\"172.24.\") ||\n hostname.startsWith(\"172.25.\") ||\n hostname.startsWith(\"172.26.\") ||\n hostname.startsWith(\"172.27.\") ||\n hostname.startsWith(\"172.28.\") ||\n hostname.startsWith(\"172.29.\") ||\n hostname.startsWith(\"172.30.\") ||\n hostname.startsWith(\"172.31.\")\n ) {\n return \"dev\";\n }\n\n return \"prod\";\n}\n\n","/**\n * Event context helpers: distinct_id, session_id, page info.\n * All browser-safe — no-ops when window is unavailable.\n */\n\nconst DISTINCT_ID_KEY = \"probat:distinct_id\";\nconst SESSION_ID_KEY = \"probat:session_id\";\n\nlet cachedDistinctId: string | null = null;\nlet cachedSessionId: string | null = null;\n\nfunction generateId(): string {\n // crypto.randomUUID where available, else fallback\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // fallback: random hex\n const bytes = new Uint8Array(16);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function getDistinctId(): string {\n if (cachedDistinctId) return cachedDistinctId;\n if (typeof window === \"undefined\") return \"server\";\n try {\n const stored = localStorage.getItem(DISTINCT_ID_KEY);\n if (stored) {\n cachedDistinctId = stored;\n return stored;\n }\n } catch {}\n const id = `anon_${generateId()}`;\n cachedDistinctId = id;\n try {\n localStorage.setItem(DISTINCT_ID_KEY, id);\n } catch {}\n return id;\n}\n\nexport function getSessionId(): string {\n if (cachedSessionId) return cachedSessionId;\n if (typeof window === \"undefined\") return \"server\";\n try {\n const stored = sessionStorage.getItem(SESSION_ID_KEY);\n if (stored) {\n cachedSessionId = stored;\n return stored;\n }\n } catch {}\n const id = `sess_${generateId()}`;\n cachedSessionId = id;\n try {\n sessionStorage.setItem(SESSION_ID_KEY, id);\n } catch {}\n return id;\n}\n\nexport function getPageKey(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.pathname + window.location.search;\n}\n\nexport function getPageUrl(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.href;\n}\n\nexport function getReferrer(): string {\n if (typeof document === \"undefined\") return \"\";\n return document.referrer;\n}\n\nexport interface EventContext {\n distinct_id: string;\n session_id: string;\n $page_url: string;\n $pathname: string;\n $referrer: string;\n}\n\nexport function buildEventContext(): EventContext {\n return {\n distinct_id: getDistinctId(),\n session_id: getSessionId(),\n $page_url: getPageUrl(),\n $pathname: typeof window !== \"undefined\" ? window.location.pathname : \"\",\n $referrer: getReferrer(),\n };\n}\n","import { detectEnvironment } from \"./environment\";\nimport { buildEventContext } from \"./eventContext\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface DecisionResponse {\n variant_key: string;\n}\n\nexport interface MetricPayload {\n event: string;\n environment: \"dev\" | \"prod\";\n properties: Record<string, unknown>;\n}\n\n// ── Assignment fetching ────────────────────────────────────────────────────\n\nconst pendingDecisions = new Map<string, Promise<string>>();\n\n/**\n * Fetch the variant assignment for an experiment.\n * Returns the variant key string (e.g. \"control\", \"ai_v1\").\n * Deduplicates concurrent calls for the same experiment.\n */\nexport async function fetchDecision(\n host: string,\n experimentId: string,\n distinctId: string\n): Promise<string> {\n const existing = pendingDecisions.get(experimentId);\n if (existing) return existing;\n\n const promise = (async () => {\n try {\n const url = `${host.replace(/\\/$/, \"\")}/experiment/decide`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n credentials: \"include\",\n body: JSON.stringify({\n experiment_id: experimentId,\n distinct_id: distinctId,\n }),\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const data = (await res.json()) as DecisionResponse;\n return data.variant_key || \"control\";\n } finally {\n pendingDecisions.delete(experimentId);\n }\n })();\n\n pendingDecisions.set(experimentId, promise);\n return promise;\n}\n\n// ── Metric sending ─────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget metric send. Never throws.\n */\nexport function sendMetric(\n host: string,\n event: string,\n properties: Record<string, unknown>\n): void {\n if (typeof window === \"undefined\") return;\n\n const ctx = buildEventContext();\n const payload: MetricPayload = {\n event,\n environment: detectEnvironment(),\n properties: {\n ...ctx,\n source: \"react-sdk\",\n captured_at: new Date().toISOString(),\n ...properties,\n },\n };\n\n try {\n const url = `${host.replace(/\\/$/, \"\")}/experiment/metrics`;\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify(payload),\n }).catch(() => {});\n } catch {\n // silently drop\n }\n}\n\n// ── Click metadata extraction ──────────────────────────────────────────────\n\nexport interface ClickMeta {\n click_target_tag: string;\n click_target_text?: string;\n click_target_id?: string;\n click_is_primary: boolean;\n}\n\n/**\n * Given a click event inside an experiment boundary, extract metadata.\n * Prioritizes elements with data-probat-click=\"primary\",\n * then falls back to button/a/role=button.\n */\nexport function extractClickMeta(target: EventTarget | null): ClickMeta | null {\n if (!target || !(target instanceof HTMLElement)) return null;\n\n // Priority 1: explicit primary marker\n const primary = target.closest('[data-probat-click=\"primary\"]');\n if (primary) return buildMeta(primary as HTMLElement, true);\n\n // Priority 2: interactive elements\n const interactive = target.closest('button, a, [role=\"button\"]');\n if (interactive) return buildMeta(interactive as HTMLElement, false);\n\n return null;\n}\n\nfunction buildMeta(el: HTMLElement, isPrimary: boolean): ClickMeta {\n const meta: ClickMeta = {\n click_target_tag: el.tagName,\n click_is_primary: isPrimary,\n };\n if (el.id) meta.click_target_id = el.id;\n const text = el.textContent?.trim();\n if (text) meta.click_target_text = text.slice(0, 120);\n return meta;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { fetchDecision } from \"../utils/api\";\nimport { getDistinctId } from \"../utils/eventContext\";\n\n// ── localStorage assignment cache ──────────────────────────────────────────\n\nconst ASSIGNMENT_PREFIX = \"probat:assignment:\";\n\ninterface StoredAssignment {\n variantKey: string;\n ts: number;\n}\n\nexport function readAssignment(id: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(ASSIGNMENT_PREFIX + id);\n if (!raw) return null;\n const parsed: StoredAssignment = JSON.parse(raw);\n return parsed.variantKey ?? null;\n } catch {\n return null;\n }\n}\n\nexport function writeAssignment(id: string, variantKey: string): void {\n if (typeof window === \"undefined\") return;\n try {\n const entry: StoredAssignment = { variantKey, ts: Date.now() };\n localStorage.setItem(ASSIGNMENT_PREFIX + id, JSON.stringify(entry));\n } catch {}\n}\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface UseExperimentOptions {\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions to console */\n debug?: boolean;\n}\n\nexport interface UseExperimentReturn {\n /** The resolved variant key (e.g. \"control\", \"ai_v1\") */\n variantKey: string;\n /** Whether the assignment has been resolved (bootstrap, cache, or fetch) */\n resolved: boolean;\n}\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Resolves the variant assignment for an experiment.\n * No tracking — use `useTrack` or `<Track>` to observe impressions/clicks.\n *\n * Priority: bootstrap > localStorage cache > fetchDecision.\n */\nexport function useExperiment(\n id: string,\n options: UseExperimentOptions = {}\n): UseExperimentReturn {\n const { fallback = \"control\", debug = false } = options;\n const { host, bootstrap, customerId } = useProbatContext();\n\n const [variantKey, setVariantKey] = useState<string>(() => {\n if (bootstrap[id]) return bootstrap[id];\n return \"control\";\n });\n const [resolved, setResolved] = useState<boolean>(() => {\n return !!bootstrap[id];\n });\n\n useEffect(() => {\n if (bootstrap[id] || readAssignment(id)) {\n const key = bootstrap[id] ?? readAssignment(id) ?? \"control\";\n setVariantKey(key);\n setResolved(true);\n return;\n }\n\n let cancelled = false;\n\n (async () => {\n try {\n const distinctId = customerId ?? getDistinctId();\n const key = await fetchDecision(host, id, distinctId);\n if (cancelled) return;\n\n setVariantKey(key);\n writeAssignment(id, key);\n } catch (err) {\n if (cancelled) return;\n if (debug) {\n console.error(`[probat] fetchDecision failed for \"${id}\":`, err);\n }\n if (fallback === \"suspend\") throw err;\n setVariantKey(\"control\");\n } finally {\n if (!cancelled) setResolved(true);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [id, host]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n if (debug && resolved) {\n console.log(`[probat] Experiment \"${id}\" -> variant \"${variantKey}\"`);\n }\n }, [debug, id, variantKey, resolved]);\n\n return { variantKey, resolved };\n}\n","/**\n * Dedupe storage for experiment exposures.\n * Uses sessionStorage + in-memory Set fallback.\n * Key format: probat:seen:{id}:{variantKey}:{instanceId}:{pageKey}\n */\n\nconst PREFIX = \"probat:seen:\";\nconst memorySet = new Set<string>();\n\nexport function makeDedupeKey(\n experimentId: string,\n variantKey: string,\n instanceId: string,\n pageKey: string\n): string {\n return `${PREFIX}${experimentId}:${variantKey}:${instanceId}:${pageKey}`;\n}\n\nexport function hasSeen(key: string): boolean {\n if (memorySet.has(key)) return true;\n if (typeof window === \"undefined\") return false;\n try {\n return sessionStorage.getItem(key) === \"1\";\n } catch {\n return false;\n }\n}\n\nexport function markSeen(key: string): void {\n memorySet.add(key);\n if (typeof window === \"undefined\") return;\n try {\n sessionStorage.setItem(key, \"1\");\n } catch {}\n}\n\n/** Reset all dedupe state — useful for testing. */\nexport function resetDedupe(): void {\n memorySet.clear();\n}\n","/**\n * Stable auto-generated instance IDs for <Experiment />.\n *\n * Problem: a naïve useRef + module counter gives a different ID on every mount,\n * so StrictMode double-mount or unmount/remount changes the dedupe key.\n *\n * Solution:\n * 1. React 18+ → useId() is stable per fiber position.\n * 2. Fallback → sessionStorage-backed slot counter per (experimentId, pageKey).\n * 3. Both paths persist a mapping in sessionStorage:\n * probat:instance:{experimentId}:{pageKey}:{positionKey} → stableId\n * so the same position resolves to the same ID across mounts.\n */\n\nimport React, { useRef } from \"react\";\nimport { getPageKey } from \"./eventContext\";\n\nconst INSTANCE_PREFIX = \"probat:instance:\";\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction shortId(): string {\n const bytes = new Uint8Array(4);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 4; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/**\n * Look up or create a stable instance ID in sessionStorage.\n */\nfunction resolveStableId(storageKey: string): string {\n if (typeof window !== \"undefined\") {\n try {\n const stored = sessionStorage.getItem(storageKey);\n if (stored) return stored;\n } catch {}\n }\n const id = `inst_${shortId()}`;\n if (typeof window !== \"undefined\") {\n try {\n sessionStorage.setItem(storageKey, id);\n } catch {}\n }\n return id;\n}\n\n// ── Fallback: render-wave slot counter ─────────────────────────────────────\n// Each synchronous render batch claims sequential slots per (experimentId,\n// pageKey). A microtask resets the counters so the next batch starts at 0,\n// giving the same component position the same slot across mounts.\n\nconst slotCounters = new Map<string, number>();\nlet resetScheduled = false;\n\nfunction claimSlot(groupKey: string): number {\n const idx = slotCounters.get(groupKey) ?? 0;\n slotCounters.set(groupKey, idx + 1);\n if (!resetScheduled) {\n resetScheduled = true;\n Promise.resolve().then(() => {\n slotCounters.clear();\n resetScheduled = false;\n });\n }\n return idx;\n}\n\n// ── Hook: React 18+ path (useId available) ─────────────────────────────────\n\nfunction useStableInstanceIdV18(experimentId: string): string {\n const reactId = (React as any).useId() as string;\n const ref = useRef(\"\");\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${reactId}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Hook: fallback path (no useId) ─────────────────────────────────────────\n\nfunction useStableInstanceIdFallback(experimentId: string): string {\n const slotRef = useRef(-1);\n const ref = useRef(\"\");\n if (slotRef.current === -1) {\n slotRef.current = claimSlot(`${experimentId}:${getPageKey()}`);\n }\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${slotRef.current}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Exported hook ──────────────────────────────────────────────────────────\n// Selection is a module-level constant so the hook-call count never changes\n// between renders — safe for the rules of hooks.\n\nexport const useStableInstanceId: (experimentId: string) => string =\n typeof (React as any).useId === \"function\"\n ? useStableInstanceIdV18\n : useStableInstanceIdFallback;\n\n// ── Test utility ───────────────────────────────────────────────────────────\n\nexport function resetInstanceIdState(): void {\n slotCounters.clear();\n resetScheduled = false;\n}\n","\"use client\";\n\nimport { useEffect, useRef, useMemo, useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric, extractClickMeta } from \"../utils/api\";\nimport { getPageKey } from \"../utils/eventContext\";\nimport { makeDedupeKey, hasSeen, markSeen } from \"../utils/dedupeStorage\";\nimport { useStableInstanceId } from \"../utils/stableInstanceId\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface UseTrackOptions {\n /** Experiment identifier */\n experimentId: string;\n /** The variant key to attach to events */\n variantKey: string;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n click?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n /** Log events to console */\n debug?: boolean;\n}\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Attaches impression and click tracking to a DOM element via a ref.\n * Completely independent of variant assignment — pass the variantKey explicitly.\n *\n * @example\n * ```tsx\n * const trackRef = useTrack({ experimentId: \"pricing\", variantKey: \"ai_v1\" });\n * return <div ref={trackRef}>...</div>;\n * ```\n */\nexport function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement | null> {\n const {\n experimentId,\n variantKey,\n componentInstanceId,\n impression: trackImpression = true,\n click: trackClick = true,\n impressionEventName = \"$experiment_exposure\",\n clickEventName = \"$experiment_click\",\n debug = false,\n } = options;\n\n const { host, customerId } = useProbatContext();\n\n const autoInstanceId = useStableInstanceId(experimentId);\n const instanceId = componentInstanceId ?? autoInstanceId;\n\n const containerRef = useRef<HTMLElement | null>(null);\n const impressionSent = useRef(false);\n\n const eventProps = useMemo(\n () => ({\n experiment_id: experimentId,\n variant_key: variantKey,\n component_instance_id: instanceId,\n ...(customerId ? { distinct_id: customerId } : {}),\n }),\n [experimentId, variantKey, instanceId, customerId]\n );\n\n // ── Impression tracking via IntersectionObserver ────────────────────────\n\n useEffect(() => {\n if (!trackImpression) return;\n\n impressionSent.current = false;\n\n const pageKey = getPageKey();\n const dedupeKey = makeDedupeKey(experimentId, variantKey, instanceId, pageKey);\n\n if (hasSeen(dedupeKey)) {\n impressionSent.current = true;\n return;\n }\n\n const el = containerRef.current;\n if (!el) return;\n\n // Fallback: no IntersectionObserver (SSR, old browser)\n if (typeof IntersectionObserver === \"undefined\") {\n if (!impressionSent.current) {\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps);\n if (debug) console.log(`[probat] Impression sent (no IO) for \"${experimentId}\"`);\n }\n return;\n }\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry || impressionSent.current) return;\n\n if (entry.isIntersecting) {\n timer = setTimeout(() => {\n if (impressionSent.current) return;\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps);\n if (debug) console.log(`[probat] Impression sent for \"${experimentId}\"`);\n observer.disconnect();\n }, 250);\n } else if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n },\n { threshold: 0.5 }\n );\n\n observer.observe(el);\n\n return () => {\n observer.disconnect();\n if (timer) clearTimeout(timer);\n };\n }, [\n trackImpression,\n experimentId,\n variantKey,\n instanceId,\n host,\n impressionEventName,\n eventProps,\n debug,\n ]);\n\n // ── Click tracking ─────────────────────────────────────────────────────\n\n const handleClick = useCallback(\n (e: Event) => {\n if (!trackClick) return;\n\n const meta = extractClickMeta(e.target as EventTarget);\n if (!meta) return;\n\n sendMetric(host, clickEventName, {\n ...eventProps,\n ...meta,\n });\n if (debug) {\n console.log(`[probat] Click tracked for \"${experimentId}\"`, meta);\n }\n },\n [trackClick, host, clickEventName, eventProps, experimentId, debug]\n );\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el || !trackClick) return;\n\n el.addEventListener(\"click\", handleClick);\n return () => {\n el.removeEventListener(\"click\", handleClick);\n };\n }, [handleClick, trackClick]);\n\n return containerRef;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useExperiment } from \"../hooks/useExperiment\";\nimport { useTrack } from \"../hooks/useTrack\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface ExperimentTrackOptions {\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n primaryClick?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n}\n\nexport interface ExperimentProps {\n /** Experiment key / identifier */\n id: string;\n /** Control variant ReactNode */\n control: React.ReactNode;\n /** Named variant ReactNodes, keyed by variant key (e.g. { ai_v1: <MyVariant /> }) */\n variants: Record<string, React.ReactNode>;\n /** Tracking configuration */\n track?: ExperimentTrackOptions;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions + events to console */\n debug?: boolean;\n}\n\nexport function Experiment({\n id,\n control,\n variants,\n track,\n componentInstanceId,\n fallback = \"control\",\n debug = false,\n}: ExperimentProps) {\n // ── Assignment (decoupled) ──────────────────────────────────────────────\n\n const { variantKey: rawKey, resolved } = useExperiment(id, { fallback, debug });\n\n // Validate variant key against the ReactNode map\n const variantKey =\n rawKey === \"control\" || rawKey in variants ? rawKey : \"control\";\n\n if (debug && rawKey !== variantKey) {\n console.warn(\n `[probat] Unknown variant \"${rawKey}\" for experiment \"${id}\", falling back to control`\n );\n }\n\n // ── Tracking (decoupled) ────────────────────────────────────────────────\n\n const trackRef = useTrack({\n experimentId: id,\n variantKey,\n componentInstanceId,\n impression: resolved ? (track?.impression !== false) : false,\n click: track?.primaryClick !== false,\n impressionEventName: track?.impressionEventName,\n clickEventName: track?.clickEventName,\n debug,\n });\n\n // ── Render ─────────────────────────────────────────────────────────────\n\n const content =\n variantKey === \"control\" || !(variantKey in variants)\n ? control\n : variants[variantKey];\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-experiment={id}\n data-probat-variant={variantKey}\n style={{\n display: \"block\",\n margin: 0,\n padding: 0,\n opacity: resolved ? 1 : 0,\n transition: resolved ? \"opacity 0.15s ease-in\" : \"none\",\n }}\n >\n {content}\n </div>\n );\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useTrack, type UseTrackOptions } from \"../hooks/useTrack\";\n\nexport interface TrackProps extends UseTrackOptions {\n children: React.ReactNode;\n}\n\n/**\n * Wrapper component that attaches impression and click tracking to its children.\n * Alternative to the `useTrack` hook when you prefer a component over a ref.\n *\n * @example\n * ```tsx\n * <Track experimentId=\"pricing\" variantKey=\"ai_v1\">\n * <PricingCard />\n * </Track>\n * ```\n */\nexport function Track({ children, ...trackOptions }: TrackProps) {\n const trackRef = useTrack(trackOptions);\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-track={trackOptions.experimentId}\n data-probat-variant={trackOptions.variantKey}\n style={{ display: \"contents\" }}\n >\n {children}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric } from \"../utils/api\";\n\nexport interface UseProbatMetricsReturn {\n /**\n * Send a custom event with arbitrary properties.\n * Never throws — failures are silently dropped.\n *\n * @example\n * ```tsx\n * const { capture } = useProbatMetrics();\n * capture(\"purchase\", { revenue: 42, currency: \"USD\" });\n * ```\n */\n capture: (event: string, properties?: Record<string, unknown>) => void;\n}\n\n/**\n * Minimal metrics hook. Provides a single `capture(event, props)` function\n * that sends events to the Probat backend using the provider's host config.\n */\nexport function useProbatMetrics(): UseProbatMetricsReturn {\n const { host, customerId } = useProbatContext();\n\n const capture = useCallback(\n (event: string, properties: Record<string, unknown> = {}) => {\n sendMetric(host, event, {\n ...(customerId ? { distinct_id: customerId } : {}),\n ...properties,\n });\n },\n [host, customerId]\n );\n\n return { capture };\n}\n","\"use client\";\n\nimport React, { createContext, useContext, type ReactNode } from \"react\";\n\n/**\n * Factory that creates a typed context + hook pair for passing a variantKey\n * between components in different files without prop-drilling.\n *\n * @example\n * ```tsx\n * // experiment-context.ts\n * export const { ExperimentProvider, useVariantKey } = createExperimentContext(\"pricing-test\");\n *\n * // Parent.tsx\n * const { variantKey } = useExperiment(\"pricing-test\");\n * <ExperimentProvider value={variantKey}><Child /></ExperimentProvider>\n *\n * // Child.tsx (different file)\n * const variantKey = useVariantKey();\n * const trackRef = useTrack({ experimentId: \"pricing-test\", variantKey });\n * ```\n */\nexport function createExperimentContext(experimentId: string) {\n const Ctx = createContext<string | null>(null);\n\n function ExperimentProvider({\n value,\n children,\n }: {\n value: string;\n children: ReactNode;\n }) {\n return React.createElement(Ctx.Provider, { value }, children);\n }\n\n function useVariantKey(): string {\n const v = useContext(Ctx);\n if (v === null) {\n throw new Error(\n `useVariantKey() must be used inside <ExperimentProvider> for \"${experimentId}\"`\n );\n }\n return v;\n }\n\n return { ExperimentProvider, useVariantKey };\n}\n"]}
1
+ {"version":3,"sources":["../src/context/ProbatContext.tsx","../src/components/ProbatProviderClient.tsx","../src/utils/environment.ts","../src/utils/eventContext.ts","../src/utils/api.ts","../src/hooks/useExperiment.ts","../src/utils/dedupeStorage.ts","../src/utils/stableInstanceId.ts","../src/hooks/useTrack.ts","../src/components/Experiment.tsx","../src/components/Track.tsx","../src/hooks/useProbatMetrics.ts","../src/utils/createExperimentContext.ts"],"names":["createContext","useMemo","React","useContext","useState","useEffect","useRef","useCallback"],"mappings":"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","file":"index.js","sourcesContent":["\"use client\";\n\nimport React, { createContext, useContext, useMemo } from \"react\";\n\nexport interface ProbatContextValue {\n host: string;\n customerId?: string;\n bootstrap: Record<string, string>;\n}\n\nconst ProbatContext = createContext<ProbatContextValue | null>(null);\n\nconst DEFAULT_HOST = \"https://gushi.onrender.com\";\n\nexport interface ProbatProviderProps {\n /** Your end-user's ID. When provided, used as the distinct_id for variant\n * assignment (consistent across devices) and attached to all events. */\n customerId?: string;\n /** Base URL for the Probat API. Defaults to https://gushi.onrender.com */\n host?: string;\n /**\n * Bootstrap assignments to avoid flash on first render.\n * Map of experiment id → variant key.\n * e.g. { \"cta-copy-test\": \"ai_v1\" }\n */\n bootstrap?: Record<string, string>;\n children: React.ReactNode;\n}\n\nexport function ProbatProvider({\n customerId,\n host = DEFAULT_HOST,\n bootstrap,\n children,\n}: ProbatProviderProps) {\n const value = useMemo<ProbatContextValue>(\n () => ({\n host: host.replace(/\\/$/, \"\"),\n customerId,\n bootstrap: bootstrap ?? {},\n }),\n [customerId, host, bootstrap]\n );\n\n return (\n <ProbatContext.Provider value={value}>\n {children}\n </ProbatContext.Provider>\n );\n}\n\nexport function useProbatContext(): ProbatContextValue {\n const ctx = useContext(ProbatContext);\n if (!ctx) {\n throw new Error(\n \"useProbatContext must be used within <ProbatProviderClient>. Wrap your app with <ProbatProviderClient>.\"\n );\n }\n return ctx;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { ProbatProvider } from \"../context/ProbatContext\";\nimport type { ProbatProviderProps } from \"../context/ProbatContext\";\n\n/**\n * Client-only provider for Next.js App Router.\n * Import this in your layout/providers file.\n *\n * @example\n * ```tsx\n * // app/providers.tsx\n * \"use client\";\n * import { ProbatProviderClient } from \"@probat/react\";\n *\n * export function Providers({ children }) {\n * return (\n * <ProbatProviderClient customerId={user.id}>\n * {children}\n * </ProbatProviderClient>\n * );\n * }\n * ```\n */\nexport function ProbatProviderClient(props: ProbatProviderProps) {\n return React.createElement(ProbatProvider, props);\n}\n\nexport type { ProbatProviderProps };\n","/**\n * Detect if the code is running on localhost (development environment).\n * Returns \"dev\" for localhost, \"prod\" for production.\n */\nexport function detectEnvironment(): \"dev\" | \"prod\" {\n if (typeof window === \"undefined\") {\n return \"prod\"; // Server-side, default to prod\n }\n\n const hostname = window.location.hostname;\n\n // Check for localhost, 127.0.0.1, or local IP addresses\n if (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"0.0.0.0\" ||\n hostname.startsWith(\"192.168.\") ||\n hostname.startsWith(\"10.\") ||\n hostname.startsWith(\"172.16.\") ||\n hostname.startsWith(\"172.17.\") ||\n hostname.startsWith(\"172.18.\") ||\n hostname.startsWith(\"172.19.\") ||\n hostname.startsWith(\"172.20.\") ||\n hostname.startsWith(\"172.21.\") ||\n hostname.startsWith(\"172.22.\") ||\n hostname.startsWith(\"172.23.\") ||\n hostname.startsWith(\"172.24.\") ||\n hostname.startsWith(\"172.25.\") ||\n hostname.startsWith(\"172.26.\") ||\n hostname.startsWith(\"172.27.\") ||\n hostname.startsWith(\"172.28.\") ||\n hostname.startsWith(\"172.29.\") ||\n hostname.startsWith(\"172.30.\") ||\n hostname.startsWith(\"172.31.\")\n ) {\n return \"dev\";\n }\n\n return \"prod\";\n}\n\n","/**\n * Event context helpers: distinct_id, session_id, page info.\n * All browser-safe — no-ops when window is unavailable.\n */\n\nconst DISTINCT_ID_KEY = \"probat:distinct_id\";\nconst SESSION_ID_KEY = \"probat:session_id\";\n\nlet cachedDistinctId: string | null = null;\nlet cachedSessionId: string | null = null;\n\nfunction generateId(): string {\n // crypto.randomUUID where available, else fallback\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // fallback: random hex\n const bytes = new Uint8Array(16);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function getDistinctId(): string {\n if (cachedDistinctId) return cachedDistinctId;\n if (typeof window === \"undefined\") return \"server\";\n try {\n const stored = localStorage.getItem(DISTINCT_ID_KEY);\n if (stored) {\n cachedDistinctId = stored;\n return stored;\n }\n } catch {}\n const id = `anon_${generateId()}`;\n cachedDistinctId = id;\n try {\n localStorage.setItem(DISTINCT_ID_KEY, id);\n } catch {}\n return id;\n}\n\nexport function getSessionId(): string {\n if (cachedSessionId) return cachedSessionId;\n if (typeof window === \"undefined\") return \"server\";\n try {\n const stored = sessionStorage.getItem(SESSION_ID_KEY);\n if (stored) {\n cachedSessionId = stored;\n return stored;\n }\n } catch {}\n const id = `sess_${generateId()}`;\n cachedSessionId = id;\n try {\n sessionStorage.setItem(SESSION_ID_KEY, id);\n } catch {}\n return id;\n}\n\nexport function getPageKey(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.pathname + window.location.search;\n}\n\nexport function getPageUrl(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.href;\n}\n\nexport function getReferrer(): string {\n if (typeof document === \"undefined\") return \"\";\n return document.referrer;\n}\n\nexport interface EventContext {\n distinct_id: string;\n session_id: string;\n $page_url: string;\n $pathname: string;\n $referrer: string;\n}\n\nexport function buildEventContext(): EventContext {\n return {\n distinct_id: getDistinctId(),\n session_id: getSessionId(),\n $page_url: getPageUrl(),\n $pathname: typeof window !== \"undefined\" ? window.location.pathname : \"\",\n $referrer: getReferrer(),\n };\n}\n","import { detectEnvironment } from \"./environment\";\nimport { buildEventContext } from \"./eventContext\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface DecisionResponse {\n variant_key: string;\n}\n\nexport interface MetricPayload {\n event: string;\n environment: \"dev\" | \"prod\";\n properties: Record<string, unknown>;\n}\n\n// ── Assignment fetching ────────────────────────────────────────────────────\n\nconst pendingDecisions = new Map<string, Promise<string>>();\n\n/**\n * Fetch the variant assignment for an experiment.\n * Returns the variant key string (e.g. \"control\", \"ai_v1\").\n * Deduplicates concurrent calls for the same experiment.\n */\nexport async function fetchDecision(\n host: string,\n experimentId: string,\n distinctId: string\n): Promise<string> {\n const existing = pendingDecisions.get(experimentId);\n if (existing) return existing;\n\n const promise = (async () => {\n try {\n const url = `${host.replace(/\\/$/, \"\")}/experiment/decide`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n credentials: \"include\",\n body: JSON.stringify({\n experiment_id: experimentId,\n distinct_id: distinctId,\n }),\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const data = (await res.json()) as DecisionResponse;\n return data.variant_key || \"control\";\n } finally {\n pendingDecisions.delete(experimentId);\n }\n })();\n\n pendingDecisions.set(experimentId, promise);\n return promise;\n}\n\n// ── Metric sending ─────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget metric send. Never throws.\n */\nexport function sendMetric(\n host: string,\n event: string,\n properties: Record<string, unknown>\n): void {\n if (typeof window === \"undefined\") return;\n\n const ctx = buildEventContext();\n const payload: MetricPayload = {\n event,\n environment: detectEnvironment(),\n properties: {\n ...ctx,\n source: \"react-sdk\",\n captured_at: new Date().toISOString(),\n ...properties,\n },\n };\n\n try {\n const url = `${host.replace(/\\/$/, \"\")}/experiment/metrics`;\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify(payload),\n }).catch(() => {});\n } catch {\n // silently drop\n }\n}\n\n// ── Click metadata extraction ──────────────────────────────────────────────\n\nexport interface ClickMeta {\n click_target_tag: string;\n click_target_text?: string;\n click_target_id?: string;\n click_is_primary: boolean;\n}\n\n/**\n * Given a click event inside an experiment boundary, extract metadata.\n * Prioritizes elements with data-probat-click=\"primary\",\n * then falls back to button/a/role=button.\n */\nexport function extractClickMeta(target: EventTarget | null): ClickMeta | null {\n if (!target || !(target instanceof HTMLElement)) return null;\n\n // Priority 1: explicit primary marker\n const primary = target.closest('[data-probat-click=\"primary\"]');\n if (primary) return buildMeta(primary as HTMLElement, true);\n\n // Priority 2: interactive elements\n const interactive = target.closest('button, a, [role=\"button\"]');\n if (interactive) return buildMeta(interactive as HTMLElement, false);\n\n return null;\n}\n\nfunction buildMeta(el: HTMLElement, isPrimary: boolean): ClickMeta {\n const meta: ClickMeta = {\n click_target_tag: el.tagName,\n click_is_primary: isPrimary,\n };\n if (el.id) meta.click_target_id = el.id;\n const text = el.textContent?.trim();\n if (text) meta.click_target_text = text.slice(0, 120);\n return meta;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { fetchDecision } from \"../utils/api\";\nimport { getDistinctId } from \"../utils/eventContext\";\n\n// ── localStorage assignment cache ──────────────────────────────────────────\n\nconst ASSIGNMENT_PREFIX = \"probat:assignment:\";\n\ninterface StoredAssignment {\n variantKey: string;\n ts: number;\n}\n\nexport function readAssignment(id: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(ASSIGNMENT_PREFIX + id);\n if (!raw) return null;\n const parsed: StoredAssignment = JSON.parse(raw);\n return parsed.variantKey ?? null;\n } catch {\n return null;\n }\n}\n\nexport function writeAssignment(id: string, variantKey: string): void {\n if (typeof window === \"undefined\") return;\n try {\n const entry: StoredAssignment = { variantKey, ts: Date.now() };\n localStorage.setItem(ASSIGNMENT_PREFIX + id, JSON.stringify(entry));\n } catch {}\n}\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface UseExperimentOptions {\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions to console */\n debug?: boolean;\n}\n\nexport interface UseExperimentReturn {\n /** The resolved variant key (e.g. \"control\", \"ai_v1\") */\n variantKey: string;\n /** Whether the assignment has been resolved (bootstrap, cache, or fetch) */\n resolved: boolean;\n}\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Resolves the variant assignment for an experiment.\n * No tracking — use `useTrack` or `<Track>` to observe impressions/clicks.\n *\n * Priority: bootstrap > localStorage cache > fetchDecision.\n */\nexport function useExperiment(\n id: string,\n options: UseExperimentOptions = {}\n): UseExperimentReturn {\n const { fallback = \"control\", debug = false } = options;\n const { host, bootstrap, customerId } = useProbatContext();\n\n const [variantKey, setVariantKey] = useState<string>(() => {\n if (bootstrap[id]) return bootstrap[id];\n return \"control\";\n });\n const [resolved, setResolved] = useState<boolean>(() => {\n return !!bootstrap[id];\n });\n\n useEffect(() => {\n if (bootstrap[id] || readAssignment(id)) {\n const key = bootstrap[id] ?? readAssignment(id) ?? \"control\";\n setVariantKey(key);\n setResolved(true);\n return;\n }\n\n let cancelled = false;\n\n (async () => {\n try {\n const distinctId = customerId ?? getDistinctId();\n const key = await fetchDecision(host, id, distinctId);\n if (cancelled) return;\n\n setVariantKey(key);\n writeAssignment(id, key);\n } catch (err) {\n if (cancelled) return;\n if (debug) {\n console.error(`[probat] fetchDecision failed for \"${id}\":`, err);\n }\n if (fallback === \"suspend\") throw err;\n setVariantKey(\"control\");\n } finally {\n if (!cancelled) setResolved(true);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [id, host]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n if (debug && resolved) {\n console.log(`[probat] Experiment \"${id}\" -> variant \"${variantKey}\"`);\n }\n }, [debug, id, variantKey, resolved]);\n\n return { variantKey, resolved };\n}\n","/**\n * Dedupe storage for experiment exposures.\n * Uses sessionStorage + in-memory Set fallback.\n * Key format: probat:seen:{id}:{variantKey}:{instanceId}:{pageKey}\n */\n\nconst PREFIX = \"probat:seen:\";\nconst memorySet = new Set<string>();\n\nexport function makeDedupeKey(\n experimentId: string,\n variantKey: string,\n instanceId: string,\n pageKey: string\n): string {\n return `${PREFIX}${experimentId}:${variantKey}:${instanceId}:${pageKey}`;\n}\n\nexport function hasSeen(key: string): boolean {\n if (memorySet.has(key)) return true;\n if (typeof window === \"undefined\") return false;\n try {\n return sessionStorage.getItem(key) === \"1\";\n } catch {\n return false;\n }\n}\n\nexport function markSeen(key: string): void {\n memorySet.add(key);\n if (typeof window === \"undefined\") return;\n try {\n sessionStorage.setItem(key, \"1\");\n } catch {}\n}\n\n/** Reset all dedupe state — useful for testing. */\nexport function resetDedupe(): void {\n memorySet.clear();\n}\n","/**\n * Stable auto-generated instance IDs for <Experiment />.\n *\n * Problem: a naïve useRef + module counter gives a different ID on every mount,\n * so StrictMode double-mount or unmount/remount changes the dedupe key.\n *\n * Solution:\n * 1. React 18+ → useId() is stable per fiber position.\n * 2. Fallback → sessionStorage-backed slot counter per (experimentId, pageKey).\n * 3. Both paths persist a mapping in sessionStorage:\n * probat:instance:{experimentId}:{pageKey}:{positionKey} → stableId\n * so the same position resolves to the same ID across mounts.\n */\n\nimport React, { useRef } from \"react\";\nimport { getPageKey } from \"./eventContext\";\n\nconst INSTANCE_PREFIX = \"probat:instance:\";\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction shortId(): string {\n const bytes = new Uint8Array(4);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 4; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/**\n * Look up or create a stable instance ID in sessionStorage.\n */\nfunction resolveStableId(storageKey: string): string {\n if (typeof window !== \"undefined\") {\n try {\n const stored = sessionStorage.getItem(storageKey);\n if (stored) return stored;\n } catch {}\n }\n const id = `inst_${shortId()}`;\n if (typeof window !== \"undefined\") {\n try {\n sessionStorage.setItem(storageKey, id);\n } catch {}\n }\n return id;\n}\n\n// ── Fallback: render-wave slot counter ─────────────────────────────────────\n// Each synchronous render batch claims sequential slots per (experimentId,\n// pageKey). A microtask resets the counters so the next batch starts at 0,\n// giving the same component position the same slot across mounts.\n\nconst slotCounters = new Map<string, number>();\nlet resetScheduled = false;\n\nfunction claimSlot(groupKey: string): number {\n const idx = slotCounters.get(groupKey) ?? 0;\n slotCounters.set(groupKey, idx + 1);\n if (!resetScheduled) {\n resetScheduled = true;\n Promise.resolve().then(() => {\n slotCounters.clear();\n resetScheduled = false;\n });\n }\n return idx;\n}\n\n// ── Hook: React 18+ path (useId available) ─────────────────────────────────\n\nfunction useStableInstanceIdV18(experimentId: string): string {\n const reactId = (React as any).useId() as string;\n const ref = useRef(\"\");\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${reactId}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Hook: fallback path (no useId) ─────────────────────────────────────────\n\nfunction useStableInstanceIdFallback(experimentId: string): string {\n const slotRef = useRef(-1);\n const ref = useRef(\"\");\n if (slotRef.current === -1) {\n slotRef.current = claimSlot(`${experimentId}:${getPageKey()}`);\n }\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${slotRef.current}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Exported hook ──────────────────────────────────────────────────────────\n// Selection is a module-level constant so the hook-call count never changes\n// between renders — safe for the rules of hooks.\n\nexport const useStableInstanceId: (experimentId: string) => string =\n typeof (React as any).useId === \"function\"\n ? useStableInstanceIdV18\n : useStableInstanceIdFallback;\n\n// ── Test utility ───────────────────────────────────────────────────────────\n\nexport function resetInstanceIdState(): void {\n slotCounters.clear();\n resetScheduled = false;\n}\n","\"use client\";\n\nimport { useEffect, useRef, useMemo, useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric, extractClickMeta } from \"../utils/api\";\nimport { getPageKey } from \"../utils/eventContext\";\nimport { makeDedupeKey, hasSeen, markSeen } from \"../utils/dedupeStorage\";\nimport { useStableInstanceId } from \"../utils/stableInstanceId\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\ninterface UseTrackBaseOptions {\n /** Experiment identifier */\n experimentId: string;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n click?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n /** Log events to console */\n debug?: boolean;\n}\n\n/** Explicit mode: pass the variant key directly */\nexport interface UseTrackExplicitOptions extends UseTrackBaseOptions {\n /** The variant key to attach to events */\n variantKey: string;\n customerId?: undefined;\n}\n\n/** Customer mode: backend resolves variant from assignment table */\nexport interface UseTrackCustomerOptions extends UseTrackBaseOptions {\n variantKey?: undefined;\n /** Customer ID to resolve variant server-side. Falls back to provider's customerId. */\n customerId?: string;\n}\n\nexport type UseTrackOptions = UseTrackExplicitOptions | UseTrackCustomerOptions;\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Attaches impression and click tracking to a DOM element via a ref.\n *\n * Two modes:\n * - **Explicit**: pass `variantKey` directly — stamped on every event.\n * - **Customer**: omit `variantKey` — backend resolves it from the assignment table\n * using `(experimentId, customerId)`.\n *\n * @example\n * ```tsx\n * // Explicit mode\n * const trackRef = useTrack({ experimentId: \"pricing\", variantKey: \"ai_v1\" });\n *\n * // Customer mode (backend resolves variant)\n * const trackRef = useTrack({ experimentId: \"pricing\", customerId: \"user_123\" });\n * ```\n */\nexport function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement | null> {\n const {\n experimentId,\n componentInstanceId,\n impression: trackImpression = true,\n click: trackClick = true,\n impressionEventName = \"$experiment_exposure\",\n clickEventName = \"$experiment_click\",\n debug = false,\n } = options;\n\n const variantKey = options.variantKey ?? undefined;\n const explicitCustomerId = \"customerId\" in options ? options.customerId : undefined;\n\n const { host, customerId: providerCustomerId } = useProbatContext();\n\n // In customer mode, use explicit customerId or fall back to provider's\n const resolvedCustomerId = explicitCustomerId ?? providerCustomerId;\n const isCustomerMode = !variantKey;\n\n const autoInstanceId = useStableInstanceId(experimentId);\n const instanceId = componentInstanceId ?? autoInstanceId;\n\n const containerRef = useRef<HTMLElement | null>(null);\n const impressionSent = useRef(false);\n\n // Runtime warning\n useEffect(() => {\n if (isCustomerMode && !resolvedCustomerId && debug) {\n console.warn(\n `[probat] useTrack called without variantKey and no customerId ` +\n `available for \"${experimentId}\". Events will have no variant attribution.`\n );\n }\n }, [isCustomerMode, resolvedCustomerId, experimentId, debug]);\n\n const eventProps = useMemo(\n () => ({\n experiment_id: experimentId,\n ...(variantKey ? { variant_key: variantKey } : {}),\n component_instance_id: instanceId,\n ...(resolvedCustomerId ? { distinct_id: resolvedCustomerId } : {}),\n }),\n [experimentId, variantKey, instanceId, resolvedCustomerId]\n );\n\n // ── Impression tracking via IntersectionObserver ────────────────────────\n\n // In customer mode, use customerId for dedupe instead of variantKey\n const dedupeVariant = variantKey ?? resolvedCustomerId ?? \"__anon__\";\n\n useEffect(() => {\n if (!trackImpression) return;\n\n impressionSent.current = false;\n\n const pageKey = getPageKey();\n const dedupeKey = makeDedupeKey(experimentId, dedupeVariant, instanceId, pageKey);\n\n if (hasSeen(dedupeKey)) {\n impressionSent.current = true;\n return;\n }\n\n const el = containerRef.current;\n if (!el) return;\n\n // Fallback: no IntersectionObserver (SSR, old browser)\n if (typeof IntersectionObserver === \"undefined\") {\n if (!impressionSent.current) {\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps);\n if (debug) console.log(`[probat] Impression sent (no IO) for \"${experimentId}\"`);\n }\n return;\n }\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry || impressionSent.current) return;\n\n if (entry.isIntersecting) {\n timer = setTimeout(() => {\n if (impressionSent.current) return;\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps);\n if (debug) console.log(`[probat] Impression sent for \"${experimentId}\"`);\n observer.disconnect();\n }, 250);\n } else if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n },\n { threshold: 0.5 }\n );\n\n observer.observe(el);\n\n return () => {\n observer.disconnect();\n if (timer) clearTimeout(timer);\n };\n }, [\n trackImpression,\n experimentId,\n dedupeVariant,\n instanceId,\n host,\n impressionEventName,\n eventProps,\n debug,\n ]);\n\n // ── Click tracking ─────────────────────────────────────────────────────\n\n const handleClick = useCallback(\n (e: Event) => {\n if (!trackClick) return;\n\n const meta = extractClickMeta(e.target as EventTarget);\n if (!meta) return;\n\n sendMetric(host, clickEventName, {\n ...eventProps,\n ...meta,\n });\n if (debug) {\n console.log(`[probat] Click tracked for \"${experimentId}\"`, meta);\n }\n },\n [trackClick, host, clickEventName, eventProps, experimentId, debug]\n );\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el || !trackClick) return;\n\n el.addEventListener(\"click\", handleClick);\n return () => {\n el.removeEventListener(\"click\", handleClick);\n };\n }, [handleClick, trackClick]);\n\n return containerRef;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useExperiment } from \"../hooks/useExperiment\";\nimport { useTrack } from \"../hooks/useTrack\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface ExperimentTrackOptions {\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n primaryClick?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n}\n\nexport interface ExperimentProps {\n /** Experiment key / identifier */\n id: string;\n /** Control variant ReactNode */\n control: React.ReactNode;\n /** Named variant ReactNodes, keyed by variant key (e.g. { ai_v1: <MyVariant /> }) */\n variants: Record<string, React.ReactNode>;\n /** Tracking configuration */\n track?: ExperimentTrackOptions;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions + events to console */\n debug?: boolean;\n}\n\nexport function Experiment({\n id,\n control,\n variants,\n track,\n componentInstanceId,\n fallback = \"control\",\n debug = false,\n}: ExperimentProps) {\n // ── Assignment (decoupled) ──────────────────────────────────────────────\n\n const { variantKey: rawKey, resolved } = useExperiment(id, { fallback, debug });\n\n // Validate variant key against the ReactNode map\n const variantKey =\n rawKey === \"control\" || rawKey in variants ? rawKey : \"control\";\n\n if (debug && rawKey !== variantKey) {\n console.warn(\n `[probat] Unknown variant \"${rawKey}\" for experiment \"${id}\", falling back to control`\n );\n }\n\n // ── Tracking (decoupled) ────────────────────────────────────────────────\n\n const trackRef = useTrack({\n experimentId: id,\n variantKey,\n componentInstanceId,\n impression: resolved ? (track?.impression !== false) : false,\n click: track?.primaryClick !== false,\n impressionEventName: track?.impressionEventName,\n clickEventName: track?.clickEventName,\n debug,\n });\n\n // ── Render ─────────────────────────────────────────────────────────────\n\n const content =\n variantKey === \"control\" || !(variantKey in variants)\n ? control\n : variants[variantKey];\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-experiment={id}\n data-probat-variant={variantKey}\n style={{\n display: \"block\",\n margin: 0,\n padding: 0,\n opacity: resolved ? 1 : 0,\n transition: resolved ? \"opacity 0.15s ease-in\" : \"none\",\n }}\n >\n {content}\n </div>\n );\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useTrack, type UseTrackOptions } from \"../hooks/useTrack\";\n\nexport type TrackProps = UseTrackOptions & {\n children: React.ReactNode;\n};\n\n/**\n * Wrapper component that attaches impression and click tracking to its children.\n * Alternative to the `useTrack` hook when you prefer a component over a ref.\n *\n * @example\n * ```tsx\n * <Track experimentId=\"pricing\" variantKey=\"ai_v1\">\n * <PricingCard />\n * </Track>\n * ```\n */\nexport function Track({ children, ...trackOptions }: TrackProps) {\n const trackRef = useTrack(trackOptions);\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-track={trackOptions.experimentId}\n data-probat-variant={trackOptions.variantKey ?? \"server-resolved\"}\n style={{ display: \"contents\" }}\n >\n {children}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric } from \"../utils/api\";\n\nexport interface UseProbatMetricsReturn {\n /**\n * Send a custom event with arbitrary properties.\n * Never throws — failures are silently dropped.\n *\n * @example\n * ```tsx\n * const { capture } = useProbatMetrics();\n * capture(\"purchase\", { revenue: 42, currency: \"USD\" });\n * ```\n */\n capture: (event: string, properties?: Record<string, unknown>) => void;\n}\n\n/**\n * Minimal metrics hook. Provides a single `capture(event, props)` function\n * that sends events to the Probat backend using the provider's host config.\n */\nexport function useProbatMetrics(): UseProbatMetricsReturn {\n const { host, customerId } = useProbatContext();\n\n const capture = useCallback(\n (event: string, properties: Record<string, unknown> = {}) => {\n sendMetric(host, event, {\n ...(customerId ? { distinct_id: customerId } : {}),\n ...properties,\n });\n },\n [host, customerId]\n );\n\n return { capture };\n}\n","\"use client\";\n\nimport React, { createContext, useContext, type ReactNode } from \"react\";\n\n/**\n * Factory that creates a typed context + hook pair for passing a variantKey\n * between components in different files without prop-drilling.\n *\n * @example\n * ```tsx\n * // experiment-context.ts\n * export const { ExperimentProvider, useVariantKey } = createExperimentContext(\"pricing-test\");\n *\n * // Parent.tsx\n * const { variantKey } = useExperiment(\"pricing-test\");\n * <ExperimentProvider value={variantKey}><Child /></ExperimentProvider>\n *\n * // Child.tsx (different file)\n * const variantKey = useVariantKey();\n * const trackRef = useTrack({ experimentId: \"pricing-test\", variantKey });\n * ```\n */\nexport function createExperimentContext(experimentId: string) {\n const Ctx = createContext<string | null>(null);\n\n function ExperimentProvider({\n value,\n children,\n }: {\n value: string;\n children: ReactNode;\n }) {\n return React.createElement(Ctx.Provider, { value }, children);\n }\n\n function useVariantKey(): string {\n const v = useContext(Ctx);\n if (v === null) {\n throw new Error(\n `useVariantKey() must be used inside <ExperimentProvider> for \"${experimentId}\"`\n );\n }\n return v;\n }\n\n return { ExperimentProvider, useVariantKey };\n}\n"]}
package/dist/index.mjs CHANGED
@@ -358,7 +358,6 @@ var useStableInstanceId = typeof React3.useId === "function" ? useStableInstance
358
358
  function useTrack(options) {
359
359
  const {
360
360
  experimentId,
361
- variantKey,
362
361
  componentInstanceId,
363
362
  impression: trackImpression = true,
364
363
  click: trackClick = true,
@@ -366,25 +365,37 @@ function useTrack(options) {
366
365
  clickEventName = "$experiment_click",
367
366
  debug = false
368
367
  } = options;
369
- const { host, customerId } = useProbatContext();
368
+ const variantKey = options.variantKey ?? void 0;
369
+ const explicitCustomerId = "customerId" in options ? options.customerId : void 0;
370
+ const { host, customerId: providerCustomerId } = useProbatContext();
371
+ const resolvedCustomerId = explicitCustomerId ?? providerCustomerId;
372
+ const isCustomerMode = !variantKey;
370
373
  const autoInstanceId = useStableInstanceId(experimentId);
371
374
  const instanceId = componentInstanceId ?? autoInstanceId;
372
375
  const containerRef = useRef(null);
373
376
  const impressionSent = useRef(false);
377
+ useEffect(() => {
378
+ if (isCustomerMode && !resolvedCustomerId && debug) {
379
+ console.warn(
380
+ `[probat] useTrack called without variantKey and no customerId available for "${experimentId}". Events will have no variant attribution.`
381
+ );
382
+ }
383
+ }, [isCustomerMode, resolvedCustomerId, experimentId, debug]);
374
384
  const eventProps = useMemo(
375
385
  () => ({
376
386
  experiment_id: experimentId,
377
- variant_key: variantKey,
387
+ ...variantKey ? { variant_key: variantKey } : {},
378
388
  component_instance_id: instanceId,
379
- ...customerId ? { distinct_id: customerId } : {}
389
+ ...resolvedCustomerId ? { distinct_id: resolvedCustomerId } : {}
380
390
  }),
381
- [experimentId, variantKey, instanceId, customerId]
391
+ [experimentId, variantKey, instanceId, resolvedCustomerId]
382
392
  );
393
+ const dedupeVariant = variantKey ?? resolvedCustomerId ?? "__anon__";
383
394
  useEffect(() => {
384
395
  if (!trackImpression) return;
385
396
  impressionSent.current = false;
386
397
  const pageKey = getPageKey();
387
- const dedupeKey = makeDedupeKey(experimentId, variantKey, instanceId, pageKey);
398
+ const dedupeKey = makeDedupeKey(experimentId, dedupeVariant, instanceId, pageKey);
388
399
  if (hasSeen(dedupeKey)) {
389
400
  impressionSent.current = true;
390
401
  return;
@@ -428,7 +439,7 @@ function useTrack(options) {
428
439
  }, [
429
440
  trackImpression,
430
441
  experimentId,
431
- variantKey,
442
+ dedupeVariant,
432
443
  instanceId,
433
444
  host,
434
445
  impressionEventName,
@@ -513,7 +524,7 @@ function Track({ children, ...trackOptions }) {
513
524
  {
514
525
  ref: trackRef,
515
526
  "data-probat-track": trackOptions.experimentId,
516
- "data-probat-variant": trackOptions.variantKey,
527
+ "data-probat-variant": trackOptions.variantKey ?? "server-resolved",
517
528
  style: { display: "contents" }
518
529
  },
519
530
  children
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context/ProbatContext.tsx","../src/components/ProbatProviderClient.tsx","../src/utils/environment.ts","../src/utils/eventContext.ts","../src/utils/api.ts","../src/hooks/useExperiment.ts","../src/utils/dedupeStorage.ts","../src/utils/stableInstanceId.ts","../src/hooks/useTrack.ts","../src/components/Experiment.tsx","../src/components/Track.tsx","../src/hooks/useProbatMetrics.ts","../src/utils/createExperimentContext.ts"],"names":["React","useRef","useMemo","useEffect","useCallback","createContext","useContext"],"mappings":"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","file":"index.mjs","sourcesContent":["\"use client\";\n\nimport React, { createContext, useContext, useMemo } from \"react\";\n\nexport interface ProbatContextValue {\n host: string;\n customerId?: string;\n bootstrap: Record<string, string>;\n}\n\nconst ProbatContext = createContext<ProbatContextValue | null>(null);\n\nconst DEFAULT_HOST = \"https://gushi.onrender.com\";\n\nexport interface ProbatProviderProps {\n /** Your end-user's ID. When provided, used as the distinct_id for variant\n * assignment (consistent across devices) and attached to all events. */\n customerId?: string;\n /** Base URL for the Probat API. Defaults to https://gushi.onrender.com */\n host?: string;\n /**\n * Bootstrap assignments to avoid flash on first render.\n * Map of experiment id → variant key.\n * e.g. { \"cta-copy-test\": \"ai_v1\" }\n */\n bootstrap?: Record<string, string>;\n children: React.ReactNode;\n}\n\nexport function ProbatProvider({\n customerId,\n host = DEFAULT_HOST,\n bootstrap,\n children,\n}: ProbatProviderProps) {\n const value = useMemo<ProbatContextValue>(\n () => ({\n host: host.replace(/\\/$/, \"\"),\n customerId,\n bootstrap: bootstrap ?? {},\n }),\n [customerId, host, bootstrap]\n );\n\n return (\n <ProbatContext.Provider value={value}>\n {children}\n </ProbatContext.Provider>\n );\n}\n\nexport function useProbatContext(): ProbatContextValue {\n const ctx = useContext(ProbatContext);\n if (!ctx) {\n throw new Error(\n \"useProbatContext must be used within <ProbatProviderClient>. Wrap your app with <ProbatProviderClient>.\"\n );\n }\n return ctx;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { ProbatProvider } from \"../context/ProbatContext\";\nimport type { ProbatProviderProps } from \"../context/ProbatContext\";\n\n/**\n * Client-only provider for Next.js App Router.\n * Import this in your layout/providers file.\n *\n * @example\n * ```tsx\n * // app/providers.tsx\n * \"use client\";\n * import { ProbatProviderClient } from \"@probat/react\";\n *\n * export function Providers({ children }) {\n * return (\n * <ProbatProviderClient customerId={user.id}>\n * {children}\n * </ProbatProviderClient>\n * );\n * }\n * ```\n */\nexport function ProbatProviderClient(props: ProbatProviderProps) {\n return React.createElement(ProbatProvider, props);\n}\n\nexport type { ProbatProviderProps };\n","/**\n * Detect if the code is running on localhost (development environment).\n * Returns \"dev\" for localhost, \"prod\" for production.\n */\nexport function detectEnvironment(): \"dev\" | \"prod\" {\n if (typeof window === \"undefined\") {\n return \"prod\"; // Server-side, default to prod\n }\n\n const hostname = window.location.hostname;\n\n // Check for localhost, 127.0.0.1, or local IP addresses\n if (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"0.0.0.0\" ||\n hostname.startsWith(\"192.168.\") ||\n hostname.startsWith(\"10.\") ||\n hostname.startsWith(\"172.16.\") ||\n hostname.startsWith(\"172.17.\") ||\n hostname.startsWith(\"172.18.\") ||\n hostname.startsWith(\"172.19.\") ||\n hostname.startsWith(\"172.20.\") ||\n hostname.startsWith(\"172.21.\") ||\n hostname.startsWith(\"172.22.\") ||\n hostname.startsWith(\"172.23.\") ||\n hostname.startsWith(\"172.24.\") ||\n hostname.startsWith(\"172.25.\") ||\n hostname.startsWith(\"172.26.\") ||\n hostname.startsWith(\"172.27.\") ||\n hostname.startsWith(\"172.28.\") ||\n hostname.startsWith(\"172.29.\") ||\n hostname.startsWith(\"172.30.\") ||\n hostname.startsWith(\"172.31.\")\n ) {\n return \"dev\";\n }\n\n return \"prod\";\n}\n\n","/**\n * Event context helpers: distinct_id, session_id, page info.\n * All browser-safe — no-ops when window is unavailable.\n */\n\nconst DISTINCT_ID_KEY = \"probat:distinct_id\";\nconst SESSION_ID_KEY = \"probat:session_id\";\n\nlet cachedDistinctId: string | null = null;\nlet cachedSessionId: string | null = null;\n\nfunction generateId(): string {\n // crypto.randomUUID where available, else fallback\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // fallback: random hex\n const bytes = new Uint8Array(16);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function getDistinctId(): string {\n if (cachedDistinctId) return cachedDistinctId;\n if (typeof window === \"undefined\") return \"server\";\n try {\n const stored = localStorage.getItem(DISTINCT_ID_KEY);\n if (stored) {\n cachedDistinctId = stored;\n return stored;\n }\n } catch {}\n const id = `anon_${generateId()}`;\n cachedDistinctId = id;\n try {\n localStorage.setItem(DISTINCT_ID_KEY, id);\n } catch {}\n return id;\n}\n\nexport function getSessionId(): string {\n if (cachedSessionId) return cachedSessionId;\n if (typeof window === \"undefined\") return \"server\";\n try {\n const stored = sessionStorage.getItem(SESSION_ID_KEY);\n if (stored) {\n cachedSessionId = stored;\n return stored;\n }\n } catch {}\n const id = `sess_${generateId()}`;\n cachedSessionId = id;\n try {\n sessionStorage.setItem(SESSION_ID_KEY, id);\n } catch {}\n return id;\n}\n\nexport function getPageKey(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.pathname + window.location.search;\n}\n\nexport function getPageUrl(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.href;\n}\n\nexport function getReferrer(): string {\n if (typeof document === \"undefined\") return \"\";\n return document.referrer;\n}\n\nexport interface EventContext {\n distinct_id: string;\n session_id: string;\n $page_url: string;\n $pathname: string;\n $referrer: string;\n}\n\nexport function buildEventContext(): EventContext {\n return {\n distinct_id: getDistinctId(),\n session_id: getSessionId(),\n $page_url: getPageUrl(),\n $pathname: typeof window !== \"undefined\" ? window.location.pathname : \"\",\n $referrer: getReferrer(),\n };\n}\n","import { detectEnvironment } from \"./environment\";\nimport { buildEventContext } from \"./eventContext\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface DecisionResponse {\n variant_key: string;\n}\n\nexport interface MetricPayload {\n event: string;\n environment: \"dev\" | \"prod\";\n properties: Record<string, unknown>;\n}\n\n// ── Assignment fetching ────────────────────────────────────────────────────\n\nconst pendingDecisions = new Map<string, Promise<string>>();\n\n/**\n * Fetch the variant assignment for an experiment.\n * Returns the variant key string (e.g. \"control\", \"ai_v1\").\n * Deduplicates concurrent calls for the same experiment.\n */\nexport async function fetchDecision(\n host: string,\n experimentId: string,\n distinctId: string\n): Promise<string> {\n const existing = pendingDecisions.get(experimentId);\n if (existing) return existing;\n\n const promise = (async () => {\n try {\n const url = `${host.replace(/\\/$/, \"\")}/experiment/decide`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n credentials: \"include\",\n body: JSON.stringify({\n experiment_id: experimentId,\n distinct_id: distinctId,\n }),\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const data = (await res.json()) as DecisionResponse;\n return data.variant_key || \"control\";\n } finally {\n pendingDecisions.delete(experimentId);\n }\n })();\n\n pendingDecisions.set(experimentId, promise);\n return promise;\n}\n\n// ── Metric sending ─────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget metric send. Never throws.\n */\nexport function sendMetric(\n host: string,\n event: string,\n properties: Record<string, unknown>\n): void {\n if (typeof window === \"undefined\") return;\n\n const ctx = buildEventContext();\n const payload: MetricPayload = {\n event,\n environment: detectEnvironment(),\n properties: {\n ...ctx,\n source: \"react-sdk\",\n captured_at: new Date().toISOString(),\n ...properties,\n },\n };\n\n try {\n const url = `${host.replace(/\\/$/, \"\")}/experiment/metrics`;\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify(payload),\n }).catch(() => {});\n } catch {\n // silently drop\n }\n}\n\n// ── Click metadata extraction ──────────────────────────────────────────────\n\nexport interface ClickMeta {\n click_target_tag: string;\n click_target_text?: string;\n click_target_id?: string;\n click_is_primary: boolean;\n}\n\n/**\n * Given a click event inside an experiment boundary, extract metadata.\n * Prioritizes elements with data-probat-click=\"primary\",\n * then falls back to button/a/role=button.\n */\nexport function extractClickMeta(target: EventTarget | null): ClickMeta | null {\n if (!target || !(target instanceof HTMLElement)) return null;\n\n // Priority 1: explicit primary marker\n const primary = target.closest('[data-probat-click=\"primary\"]');\n if (primary) return buildMeta(primary as HTMLElement, true);\n\n // Priority 2: interactive elements\n const interactive = target.closest('button, a, [role=\"button\"]');\n if (interactive) return buildMeta(interactive as HTMLElement, false);\n\n return null;\n}\n\nfunction buildMeta(el: HTMLElement, isPrimary: boolean): ClickMeta {\n const meta: ClickMeta = {\n click_target_tag: el.tagName,\n click_is_primary: isPrimary,\n };\n if (el.id) meta.click_target_id = el.id;\n const text = el.textContent?.trim();\n if (text) meta.click_target_text = text.slice(0, 120);\n return meta;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { fetchDecision } from \"../utils/api\";\nimport { getDistinctId } from \"../utils/eventContext\";\n\n// ── localStorage assignment cache ──────────────────────────────────────────\n\nconst ASSIGNMENT_PREFIX = \"probat:assignment:\";\n\ninterface StoredAssignment {\n variantKey: string;\n ts: number;\n}\n\nexport function readAssignment(id: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(ASSIGNMENT_PREFIX + id);\n if (!raw) return null;\n const parsed: StoredAssignment = JSON.parse(raw);\n return parsed.variantKey ?? null;\n } catch {\n return null;\n }\n}\n\nexport function writeAssignment(id: string, variantKey: string): void {\n if (typeof window === \"undefined\") return;\n try {\n const entry: StoredAssignment = { variantKey, ts: Date.now() };\n localStorage.setItem(ASSIGNMENT_PREFIX + id, JSON.stringify(entry));\n } catch {}\n}\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface UseExperimentOptions {\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions to console */\n debug?: boolean;\n}\n\nexport interface UseExperimentReturn {\n /** The resolved variant key (e.g. \"control\", \"ai_v1\") */\n variantKey: string;\n /** Whether the assignment has been resolved (bootstrap, cache, or fetch) */\n resolved: boolean;\n}\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Resolves the variant assignment for an experiment.\n * No tracking — use `useTrack` or `<Track>` to observe impressions/clicks.\n *\n * Priority: bootstrap > localStorage cache > fetchDecision.\n */\nexport function useExperiment(\n id: string,\n options: UseExperimentOptions = {}\n): UseExperimentReturn {\n const { fallback = \"control\", debug = false } = options;\n const { host, bootstrap, customerId } = useProbatContext();\n\n const [variantKey, setVariantKey] = useState<string>(() => {\n if (bootstrap[id]) return bootstrap[id];\n return \"control\";\n });\n const [resolved, setResolved] = useState<boolean>(() => {\n return !!bootstrap[id];\n });\n\n useEffect(() => {\n if (bootstrap[id] || readAssignment(id)) {\n const key = bootstrap[id] ?? readAssignment(id) ?? \"control\";\n setVariantKey(key);\n setResolved(true);\n return;\n }\n\n let cancelled = false;\n\n (async () => {\n try {\n const distinctId = customerId ?? getDistinctId();\n const key = await fetchDecision(host, id, distinctId);\n if (cancelled) return;\n\n setVariantKey(key);\n writeAssignment(id, key);\n } catch (err) {\n if (cancelled) return;\n if (debug) {\n console.error(`[probat] fetchDecision failed for \"${id}\":`, err);\n }\n if (fallback === \"suspend\") throw err;\n setVariantKey(\"control\");\n } finally {\n if (!cancelled) setResolved(true);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [id, host]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n if (debug && resolved) {\n console.log(`[probat] Experiment \"${id}\" -> variant \"${variantKey}\"`);\n }\n }, [debug, id, variantKey, resolved]);\n\n return { variantKey, resolved };\n}\n","/**\n * Dedupe storage for experiment exposures.\n * Uses sessionStorage + in-memory Set fallback.\n * Key format: probat:seen:{id}:{variantKey}:{instanceId}:{pageKey}\n */\n\nconst PREFIX = \"probat:seen:\";\nconst memorySet = new Set<string>();\n\nexport function makeDedupeKey(\n experimentId: string,\n variantKey: string,\n instanceId: string,\n pageKey: string\n): string {\n return `${PREFIX}${experimentId}:${variantKey}:${instanceId}:${pageKey}`;\n}\n\nexport function hasSeen(key: string): boolean {\n if (memorySet.has(key)) return true;\n if (typeof window === \"undefined\") return false;\n try {\n return sessionStorage.getItem(key) === \"1\";\n } catch {\n return false;\n }\n}\n\nexport function markSeen(key: string): void {\n memorySet.add(key);\n if (typeof window === \"undefined\") return;\n try {\n sessionStorage.setItem(key, \"1\");\n } catch {}\n}\n\n/** Reset all dedupe state — useful for testing. */\nexport function resetDedupe(): void {\n memorySet.clear();\n}\n","/**\n * Stable auto-generated instance IDs for <Experiment />.\n *\n * Problem: a naïve useRef + module counter gives a different ID on every mount,\n * so StrictMode double-mount or unmount/remount changes the dedupe key.\n *\n * Solution:\n * 1. React 18+ → useId() is stable per fiber position.\n * 2. Fallback → sessionStorage-backed slot counter per (experimentId, pageKey).\n * 3. Both paths persist a mapping in sessionStorage:\n * probat:instance:{experimentId}:{pageKey}:{positionKey} → stableId\n * so the same position resolves to the same ID across mounts.\n */\n\nimport React, { useRef } from \"react\";\nimport { getPageKey } from \"./eventContext\";\n\nconst INSTANCE_PREFIX = \"probat:instance:\";\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction shortId(): string {\n const bytes = new Uint8Array(4);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 4; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/**\n * Look up or create a stable instance ID in sessionStorage.\n */\nfunction resolveStableId(storageKey: string): string {\n if (typeof window !== \"undefined\") {\n try {\n const stored = sessionStorage.getItem(storageKey);\n if (stored) return stored;\n } catch {}\n }\n const id = `inst_${shortId()}`;\n if (typeof window !== \"undefined\") {\n try {\n sessionStorage.setItem(storageKey, id);\n } catch {}\n }\n return id;\n}\n\n// ── Fallback: render-wave slot counter ─────────────────────────────────────\n// Each synchronous render batch claims sequential slots per (experimentId,\n// pageKey). A microtask resets the counters so the next batch starts at 0,\n// giving the same component position the same slot across mounts.\n\nconst slotCounters = new Map<string, number>();\nlet resetScheduled = false;\n\nfunction claimSlot(groupKey: string): number {\n const idx = slotCounters.get(groupKey) ?? 0;\n slotCounters.set(groupKey, idx + 1);\n if (!resetScheduled) {\n resetScheduled = true;\n Promise.resolve().then(() => {\n slotCounters.clear();\n resetScheduled = false;\n });\n }\n return idx;\n}\n\n// ── Hook: React 18+ path (useId available) ─────────────────────────────────\n\nfunction useStableInstanceIdV18(experimentId: string): string {\n const reactId = (React as any).useId() as string;\n const ref = useRef(\"\");\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${reactId}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Hook: fallback path (no useId) ─────────────────────────────────────────\n\nfunction useStableInstanceIdFallback(experimentId: string): string {\n const slotRef = useRef(-1);\n const ref = useRef(\"\");\n if (slotRef.current === -1) {\n slotRef.current = claimSlot(`${experimentId}:${getPageKey()}`);\n }\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${slotRef.current}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Exported hook ──────────────────────────────────────────────────────────\n// Selection is a module-level constant so the hook-call count never changes\n// between renders — safe for the rules of hooks.\n\nexport const useStableInstanceId: (experimentId: string) => string =\n typeof (React as any).useId === \"function\"\n ? useStableInstanceIdV18\n : useStableInstanceIdFallback;\n\n// ── Test utility ───────────────────────────────────────────────────────────\n\nexport function resetInstanceIdState(): void {\n slotCounters.clear();\n resetScheduled = false;\n}\n","\"use client\";\n\nimport { useEffect, useRef, useMemo, useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric, extractClickMeta } from \"../utils/api\";\nimport { getPageKey } from \"../utils/eventContext\";\nimport { makeDedupeKey, hasSeen, markSeen } from \"../utils/dedupeStorage\";\nimport { useStableInstanceId } from \"../utils/stableInstanceId\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface UseTrackOptions {\n /** Experiment identifier */\n experimentId: string;\n /** The variant key to attach to events */\n variantKey: string;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n click?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n /** Log events to console */\n debug?: boolean;\n}\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Attaches impression and click tracking to a DOM element via a ref.\n * Completely independent of variant assignment — pass the variantKey explicitly.\n *\n * @example\n * ```tsx\n * const trackRef = useTrack({ experimentId: \"pricing\", variantKey: \"ai_v1\" });\n * return <div ref={trackRef}>...</div>;\n * ```\n */\nexport function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement | null> {\n const {\n experimentId,\n variantKey,\n componentInstanceId,\n impression: trackImpression = true,\n click: trackClick = true,\n impressionEventName = \"$experiment_exposure\",\n clickEventName = \"$experiment_click\",\n debug = false,\n } = options;\n\n const { host, customerId } = useProbatContext();\n\n const autoInstanceId = useStableInstanceId(experimentId);\n const instanceId = componentInstanceId ?? autoInstanceId;\n\n const containerRef = useRef<HTMLElement | null>(null);\n const impressionSent = useRef(false);\n\n const eventProps = useMemo(\n () => ({\n experiment_id: experimentId,\n variant_key: variantKey,\n component_instance_id: instanceId,\n ...(customerId ? { distinct_id: customerId } : {}),\n }),\n [experimentId, variantKey, instanceId, customerId]\n );\n\n // ── Impression tracking via IntersectionObserver ────────────────────────\n\n useEffect(() => {\n if (!trackImpression) return;\n\n impressionSent.current = false;\n\n const pageKey = getPageKey();\n const dedupeKey = makeDedupeKey(experimentId, variantKey, instanceId, pageKey);\n\n if (hasSeen(dedupeKey)) {\n impressionSent.current = true;\n return;\n }\n\n const el = containerRef.current;\n if (!el) return;\n\n // Fallback: no IntersectionObserver (SSR, old browser)\n if (typeof IntersectionObserver === \"undefined\") {\n if (!impressionSent.current) {\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps);\n if (debug) console.log(`[probat] Impression sent (no IO) for \"${experimentId}\"`);\n }\n return;\n }\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry || impressionSent.current) return;\n\n if (entry.isIntersecting) {\n timer = setTimeout(() => {\n if (impressionSent.current) return;\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps);\n if (debug) console.log(`[probat] Impression sent for \"${experimentId}\"`);\n observer.disconnect();\n }, 250);\n } else if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n },\n { threshold: 0.5 }\n );\n\n observer.observe(el);\n\n return () => {\n observer.disconnect();\n if (timer) clearTimeout(timer);\n };\n }, [\n trackImpression,\n experimentId,\n variantKey,\n instanceId,\n host,\n impressionEventName,\n eventProps,\n debug,\n ]);\n\n // ── Click tracking ─────────────────────────────────────────────────────\n\n const handleClick = useCallback(\n (e: Event) => {\n if (!trackClick) return;\n\n const meta = extractClickMeta(e.target as EventTarget);\n if (!meta) return;\n\n sendMetric(host, clickEventName, {\n ...eventProps,\n ...meta,\n });\n if (debug) {\n console.log(`[probat] Click tracked for \"${experimentId}\"`, meta);\n }\n },\n [trackClick, host, clickEventName, eventProps, experimentId, debug]\n );\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el || !trackClick) return;\n\n el.addEventListener(\"click\", handleClick);\n return () => {\n el.removeEventListener(\"click\", handleClick);\n };\n }, [handleClick, trackClick]);\n\n return containerRef;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useExperiment } from \"../hooks/useExperiment\";\nimport { useTrack } from \"../hooks/useTrack\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface ExperimentTrackOptions {\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n primaryClick?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n}\n\nexport interface ExperimentProps {\n /** Experiment key / identifier */\n id: string;\n /** Control variant ReactNode */\n control: React.ReactNode;\n /** Named variant ReactNodes, keyed by variant key (e.g. { ai_v1: <MyVariant /> }) */\n variants: Record<string, React.ReactNode>;\n /** Tracking configuration */\n track?: ExperimentTrackOptions;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions + events to console */\n debug?: boolean;\n}\n\nexport function Experiment({\n id,\n control,\n variants,\n track,\n componentInstanceId,\n fallback = \"control\",\n debug = false,\n}: ExperimentProps) {\n // ── Assignment (decoupled) ──────────────────────────────────────────────\n\n const { variantKey: rawKey, resolved } = useExperiment(id, { fallback, debug });\n\n // Validate variant key against the ReactNode map\n const variantKey =\n rawKey === \"control\" || rawKey in variants ? rawKey : \"control\";\n\n if (debug && rawKey !== variantKey) {\n console.warn(\n `[probat] Unknown variant \"${rawKey}\" for experiment \"${id}\", falling back to control`\n );\n }\n\n // ── Tracking (decoupled) ────────────────────────────────────────────────\n\n const trackRef = useTrack({\n experimentId: id,\n variantKey,\n componentInstanceId,\n impression: resolved ? (track?.impression !== false) : false,\n click: track?.primaryClick !== false,\n impressionEventName: track?.impressionEventName,\n clickEventName: track?.clickEventName,\n debug,\n });\n\n // ── Render ─────────────────────────────────────────────────────────────\n\n const content =\n variantKey === \"control\" || !(variantKey in variants)\n ? control\n : variants[variantKey];\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-experiment={id}\n data-probat-variant={variantKey}\n style={{\n display: \"block\",\n margin: 0,\n padding: 0,\n opacity: resolved ? 1 : 0,\n transition: resolved ? \"opacity 0.15s ease-in\" : \"none\",\n }}\n >\n {content}\n </div>\n );\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useTrack, type UseTrackOptions } from \"../hooks/useTrack\";\n\nexport interface TrackProps extends UseTrackOptions {\n children: React.ReactNode;\n}\n\n/**\n * Wrapper component that attaches impression and click tracking to its children.\n * Alternative to the `useTrack` hook when you prefer a component over a ref.\n *\n * @example\n * ```tsx\n * <Track experimentId=\"pricing\" variantKey=\"ai_v1\">\n * <PricingCard />\n * </Track>\n * ```\n */\nexport function Track({ children, ...trackOptions }: TrackProps) {\n const trackRef = useTrack(trackOptions);\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-track={trackOptions.experimentId}\n data-probat-variant={trackOptions.variantKey}\n style={{ display: \"contents\" }}\n >\n {children}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric } from \"../utils/api\";\n\nexport interface UseProbatMetricsReturn {\n /**\n * Send a custom event with arbitrary properties.\n * Never throws — failures are silently dropped.\n *\n * @example\n * ```tsx\n * const { capture } = useProbatMetrics();\n * capture(\"purchase\", { revenue: 42, currency: \"USD\" });\n * ```\n */\n capture: (event: string, properties?: Record<string, unknown>) => void;\n}\n\n/**\n * Minimal metrics hook. Provides a single `capture(event, props)` function\n * that sends events to the Probat backend using the provider's host config.\n */\nexport function useProbatMetrics(): UseProbatMetricsReturn {\n const { host, customerId } = useProbatContext();\n\n const capture = useCallback(\n (event: string, properties: Record<string, unknown> = {}) => {\n sendMetric(host, event, {\n ...(customerId ? { distinct_id: customerId } : {}),\n ...properties,\n });\n },\n [host, customerId]\n );\n\n return { capture };\n}\n","\"use client\";\n\nimport React, { createContext, useContext, type ReactNode } from \"react\";\n\n/**\n * Factory that creates a typed context + hook pair for passing a variantKey\n * between components in different files without prop-drilling.\n *\n * @example\n * ```tsx\n * // experiment-context.ts\n * export const { ExperimentProvider, useVariantKey } = createExperimentContext(\"pricing-test\");\n *\n * // Parent.tsx\n * const { variantKey } = useExperiment(\"pricing-test\");\n * <ExperimentProvider value={variantKey}><Child /></ExperimentProvider>\n *\n * // Child.tsx (different file)\n * const variantKey = useVariantKey();\n * const trackRef = useTrack({ experimentId: \"pricing-test\", variantKey });\n * ```\n */\nexport function createExperimentContext(experimentId: string) {\n const Ctx = createContext<string | null>(null);\n\n function ExperimentProvider({\n value,\n children,\n }: {\n value: string;\n children: ReactNode;\n }) {\n return React.createElement(Ctx.Provider, { value }, children);\n }\n\n function useVariantKey(): string {\n const v = useContext(Ctx);\n if (v === null) {\n throw new Error(\n `useVariantKey() must be used inside <ExperimentProvider> for \"${experimentId}\"`\n );\n }\n return v;\n }\n\n return { ExperimentProvider, useVariantKey };\n}\n"]}
1
+ {"version":3,"sources":["../src/context/ProbatContext.tsx","../src/components/ProbatProviderClient.tsx","../src/utils/environment.ts","../src/utils/eventContext.ts","../src/utils/api.ts","../src/hooks/useExperiment.ts","../src/utils/dedupeStorage.ts","../src/utils/stableInstanceId.ts","../src/hooks/useTrack.ts","../src/components/Experiment.tsx","../src/components/Track.tsx","../src/hooks/useProbatMetrics.ts","../src/utils/createExperimentContext.ts"],"names":["React","useRef","useEffect","useMemo","useCallback","createContext","useContext"],"mappings":";;AAUA,IAAM,aAAA,GAAgB,cAAyC,IAAI,CAAA;AAEnE,IAAM,YAAA,GAAe,4BAAA;AAiBd,SAAS,cAAA,CAAe;AAAA,EAC3B,UAAA;AAAA,EACA,IAAA,GAAO,YAAA;AAAA,EACP,SAAA;AAAA,EACA;AACJ,CAAA,EAAwB;AACpB,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACV,OAAO;AAAA,MACH,IAAA,EAAM,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAAA,MAC5B,UAAA;AAAA,MACA,SAAA,EAAW,aAAa;AAAC,KAC7B,CAAA;AAAA,IACA,CAAC,UAAA,EAAY,IAAA,EAAM,SAAS;AAAA,GAChC;AAEA,EAAA,uBACIA,MAAA,CAAA,aAAA,CAAC,aAAA,CAAc,QAAA,EAAd,EAAuB,SACnB,QACL,CAAA;AAER;AAEO,SAAS,gBAAA,GAAuC;AACnD,EAAA,MAAM,GAAA,GAAM,WAAW,aAAa,CAAA;AACpC,EAAA,IAAI,CAAC,GAAA,EAAK;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACN;AAAA,KACJ;AAAA,EACJ;AACA,EAAA,OAAO,GAAA;AACX;;;AClCO,SAAS,qBAAqB,KAAA,EAA4B;AAC7D,EAAA,OAAOA,MAAAA,CAAM,aAAA,CAAc,cAAA,EAAgB,KAAK,CAAA;AACpD;;;ACvBO,SAAS,iBAAA,GAAoC;AAChD,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAC/B,IAAA,OAAO,MAAA;AAAA,EACX;AAEA,EAAA,MAAM,QAAA,GAAW,OAAO,QAAA,CAAS,QAAA;AAGjC,EAAA,IACI,aAAa,WAAA,IACb,QAAA,KAAa,WAAA,IACb,QAAA,KAAa,aACb,QAAA,CAAS,UAAA,CAAW,UAAU,CAAA,IAC9B,SAAS,UAAA,CAAW,KAAK,CAAA,IACzB,QAAA,CAAS,WAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,KAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,SAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,WAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,KAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,SAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,WAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,KAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,IAC7B,SAAS,UAAA,CAAW,SAAS,KAC7B,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,EAC/B;AACE,IAAA,OAAO,KAAA;AAAA,EACX;AAEA,EAAA,OAAO,MAAA;AACX;;;AClCA,IAAM,eAAA,GAAkB,oBAAA;AACxB,IAAM,cAAA,GAAiB,mBAAA;AAEvB,IAAI,gBAAA,GAAkC,IAAA;AACtC,IAAI,eAAA,GAAiC,IAAA;AAErC,SAAS,UAAA,GAAqB;AAE1B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,UAAA,EAAY;AACpD,IAAA,OAAO,OAAO,UAAA,EAAW;AAAA,EAC7B;AAEA,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,EAAE,CAAA;AAC/B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,eAAA,EAAiB;AACzD,IAAA,MAAA,CAAO,gBAAgB,KAAK,CAAA;AAAA,EAChC,CAAA,MAAO;AACH,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,EAAA,EAAI,CAAA,EAAA,EAAK,KAAA,CAAM,CAAC,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAA,KAAW,GAAG,CAAA;AAAA,EAC1E;AACA,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,SAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAC5E;AAEO,SAAS,aAAA,GAAwB;AACpC,EAAA,IAAI,kBAAkB,OAAO,gBAAA;AAC7B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,QAAA;AAC1C,EAAA,IAAI;AACA,IAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,eAAe,CAAA;AACnD,IAAA,IAAI,MAAA,EAAQ;AACR,MAAA,gBAAA,GAAmB,MAAA;AACnB,MAAA,OAAO,MAAA;AAAA,IACX;AAAA,EACJ,CAAA,CAAA,MAAQ;AAAA,EAAC;AACT,EAAA,MAAM,EAAA,GAAK,CAAA,KAAA,EAAQ,UAAA,EAAY,CAAA,CAAA;AAC/B,EAAA,gBAAA,GAAmB,EAAA;AACnB,EAAA,IAAI;AACA,IAAA,YAAA,CAAa,OAAA,CAAQ,iBAAiB,EAAE,CAAA;AAAA,EAC5C,CAAA,CAAA,MAAQ;AAAA,EAAC;AACT,EAAA,OAAO,EAAA;AACX;AAEO,SAAS,YAAA,GAAuB;AACnC,EAAA,IAAI,iBAAiB,OAAO,eAAA;AAC5B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,QAAA;AAC1C,EAAA,IAAI;AACA,IAAA,MAAM,MAAA,GAAS,cAAA,CAAe,OAAA,CAAQ,cAAc,CAAA;AACpD,IAAA,IAAI,MAAA,EAAQ;AACR,MAAA,eAAA,GAAkB,MAAA;AAClB,MAAA,OAAO,MAAA;AAAA,IACX;AAAA,EACJ,CAAA,CAAA,MAAQ;AAAA,EAAC;AACT,EAAA,MAAM,EAAA,GAAK,CAAA,KAAA,EAAQ,UAAA,EAAY,CAAA,CAAA;AAC/B,EAAA,eAAA,GAAkB,EAAA;AAClB,EAAA,IAAI;AACA,IAAA,cAAA,CAAe,OAAA,CAAQ,gBAAgB,EAAE,CAAA;AAAA,EAC7C,CAAA,CAAA,MAAQ;AAAA,EAAC;AACT,EAAA,OAAO,EAAA;AACX;AAEO,SAAS,UAAA,GAAqB;AACjC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,EAAA;AAC1C,EAAA,OAAO,MAAA,CAAO,QAAA,CAAS,QAAA,GAAW,MAAA,CAAO,QAAA,CAAS,MAAA;AACtD;AAEO,SAAS,UAAA,GAAqB;AACjC,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,EAAA;AAC1C,EAAA,OAAO,OAAO,QAAA,CAAS,IAAA;AAC3B;AAEO,SAAS,WAAA,GAAsB;AAClC,EAAA,IAAI,OAAO,QAAA,KAAa,WAAA,EAAa,OAAO,EAAA;AAC5C,EAAA,OAAO,QAAA,CAAS,QAAA;AACpB;AAUO,SAAS,iBAAA,GAAkC;AAC9C,EAAA,OAAO;AAAA,IACH,aAAa,aAAA,EAAc;AAAA,IAC3B,YAAY,YAAA,EAAa;AAAA,IACzB,WAAW,UAAA,EAAW;AAAA,IACtB,WAAW,OAAO,MAAA,KAAW,WAAA,GAAc,MAAA,CAAO,SAAS,QAAA,GAAW,EAAA;AAAA,IACtE,WAAW,WAAA;AAAY,GAC3B;AACJ;;;AC5EA,IAAM,gBAAA,uBAAuB,GAAA,EAA6B;AAO1D,eAAsB,aAAA,CAClB,IAAA,EACA,YAAA,EACA,UAAA,EACe;AACf,EAAA,MAAM,QAAA,GAAW,gBAAA,CAAiB,GAAA,CAAI,YAAY,CAAA;AAClD,EAAA,IAAI,UAAU,OAAO,QAAA;AAErB,EAAA,MAAM,WAAW,YAAY;AACzB,IAAA,IAAI;AACA,MAAA,MAAM,MAAM,CAAA,EAAG,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,kBAAA,CAAA;AACtC,MAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,GAAA,EAAK;AAAA,QACzB,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACL,cAAA,EAAgB,kBAAA;AAAA,UAChB,MAAA,EAAQ;AAAA,SACZ;AAAA,QACA,WAAA,EAAa,SAAA;AAAA,QACb,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,UACjB,aAAA,EAAe,YAAA;AAAA,UACf,WAAA,EAAa;AAAA,SAChB;AAAA,OACJ,CAAA;AACD,MAAA,IAAI,CAAC,IAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,KAAA,EAAQ,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AACjD,MAAA,MAAM,IAAA,GAAQ,MAAM,GAAA,CAAI,IAAA,EAAK;AAC7B,MAAA,OAAO,KAAK,WAAA,IAAe,SAAA;AAAA,IAC/B,CAAA,SAAE;AACE,MAAA,gBAAA,CAAiB,OAAO,YAAY,CAAA;AAAA,IACxC;AAAA,EACJ,CAAA,GAAG;AAEH,EAAA,gBAAA,CAAiB,GAAA,CAAI,cAAc,OAAO,CAAA;AAC1C,EAAA,OAAO,OAAA;AACX;AAOO,SAAS,UAAA,CACZ,IAAA,EACA,KAAA,EACA,UAAA,EACI;AACJ,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAEnC,EAAA,MAAM,MAAM,iBAAA,EAAkB;AAC9B,EAAA,MAAM,OAAA,GAAyB;AAAA,IAC3B,KAAA;AAAA,IACA,aAAa,iBAAA,EAAkB;AAAA,IAC/B,UAAA,EAAY;AAAA,MACR,GAAG,GAAA;AAAA,MACH,MAAA,EAAQ,WAAA;AAAA,MACR,WAAA,EAAA,iBAAa,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,MACpC,GAAG;AAAA;AACP,GACJ;AAEA,EAAA,IAAI;AACA,IAAA,MAAM,MAAM,CAAA,EAAG,IAAA,CAAK,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAC,CAAA,mBAAA,CAAA;AACtC,IAAA,KAAA,CAAM,GAAA,EAAK;AAAA,MACP,MAAA,EAAQ,MAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAmB;AAAA,MAC9C,WAAA,EAAa,SAAA;AAAA,MACb,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,KAC/B,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,IAAC,CAAC,CAAA;AAAA,EACrB,CAAA,CAAA,MAAQ;AAAA,EAER;AACJ;AAgBO,SAAS,iBAAiB,MAAA,EAA8C;AAC3E,EAAA,IAAI,CAAC,MAAA,IAAU,EAAE,MAAA,YAAkB,cAAc,OAAO,IAAA;AAGxD,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,CAAQ,+BAA+B,CAAA;AAC9D,EAAA,IAAI,OAAA,EAAS,OAAO,SAAA,CAAU,OAAA,EAAwB,IAAI,CAAA;AAG1D,EAAA,MAAM,WAAA,GAAc,MAAA,CAAO,OAAA,CAAQ,4BAA4B,CAAA;AAC/D,EAAA,IAAI,WAAA,EAAa,OAAO,SAAA,CAAU,WAAA,EAA4B,KAAK,CAAA;AAEnE,EAAA,OAAO,IAAA;AACX;AAEA,SAAS,SAAA,CAAU,IAAiB,SAAA,EAA+B;AAC/D,EAAA,MAAM,IAAA,GAAkB;AAAA,IACpB,kBAAkB,EAAA,CAAG,OAAA;AAAA,IACrB,gBAAA,EAAkB;AAAA,GACtB;AACA,EAAA,IAAI,EAAA,CAAG,EAAA,EAAI,IAAA,CAAK,eAAA,GAAkB,EAAA,CAAG,EAAA;AACrC,EAAA,MAAM,IAAA,GAAO,EAAA,CAAG,WAAA,EAAa,IAAA,EAAK;AAClC,EAAA,IAAI,MAAM,IAAA,CAAK,iBAAA,GAAoB,IAAA,CAAK,KAAA,CAAM,GAAG,GAAG,CAAA;AACpD,EAAA,OAAO,IAAA;AACX;;;AC5HA,IAAM,iBAAA,GAAoB,oBAAA;AAOnB,SAAS,eAAe,EAAA,EAA2B;AACtD,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,IAAI;AACA,IAAA,MAAM,GAAA,GAAM,YAAA,CAAa,OAAA,CAAQ,iBAAA,GAAoB,EAAE,CAAA;AACvD,IAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,IAAA,MAAM,MAAA,GAA2B,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAC/C,IAAA,OAAO,OAAO,UAAA,IAAc,IAAA;AAAA,EAChC,CAAA,CAAA,MAAQ;AACJ,IAAA,OAAO,IAAA;AAAA,EACX;AACJ;AAEO,SAAS,eAAA,CAAgB,IAAY,UAAA,EAA0B;AAClE,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACA,IAAA,MAAM,QAA0B,EAAE,UAAA,EAAY,EAAA,EAAI,IAAA,CAAK,KAAI,EAAE;AAC7D,IAAA,YAAA,CAAa,QAAQ,iBAAA,GAAoB,EAAA,EAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAAA,EACtE,CAAA,CAAA,MAAQ;AAAA,EAAC;AACb;AA0BO,SAAS,aAAA,CACZ,EAAA,EACA,OAAA,GAAgC,EAAC,EACd;AACnB,EAAA,MAAM,EAAE,QAAA,GAAW,SAAA,EAAW,KAAA,GAAQ,OAAM,GAAI,OAAA;AAChD,EAAA,MAAM,EAAE,IAAA,EAAM,SAAA,EAAW,UAAA,KAAe,gBAAA,EAAiB;AAEzD,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAiB,MAAM;AACvD,IAAA,IAAI,SAAA,CAAU,EAAE,CAAA,EAAG,OAAO,UAAU,EAAE,CAAA;AACtC,IAAA,OAAO,SAAA;AAAA,EACX,CAAC,CAAA;AACD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAkB,MAAM;AACpD,IAAA,OAAO,CAAC,CAAC,SAAA,CAAU,EAAE,CAAA;AAAA,EACzB,CAAC,CAAA;AAED,EAAA,SAAA,CAAU,MAAM;AACZ,IAAA,IAAI,SAAA,CAAU,EAAE,CAAA,IAAK,cAAA,CAAe,EAAE,CAAA,EAAG;AACrC,MAAA,MAAM,MAAM,SAAA,CAAU,EAAE,CAAA,IAAK,cAAA,CAAe,EAAE,CAAA,IAAK,SAAA;AACnD,MAAA,aAAA,CAAc,GAAG,CAAA;AACjB,MAAA,WAAA,CAAY,IAAI,CAAA;AAChB,MAAA;AAAA,IACJ;AAEA,IAAA,IAAI,SAAA,GAAY,KAAA;AAEhB,IAAA,CAAC,YAAY;AACT,MAAA,IAAI;AACA,QAAA,MAAM,UAAA,GAAa,cAAc,aAAA,EAAc;AAC/C,QAAA,MAAM,GAAA,GAAM,MAAM,aAAA,CAAc,IAAA,EAAM,IAAI,UAAU,CAAA;AACpD,QAAA,IAAI,SAAA,EAAW;AAEf,QAAA,aAAA,CAAc,GAAG,CAAA;AACjB,QAAA,eAAA,CAAgB,IAAI,GAAG,CAAA;AAAA,MAC3B,SAAS,GAAA,EAAK;AACV,QAAA,IAAI,SAAA,EAAW;AACf,QAAA,IAAI,KAAA,EAAO;AACP,UAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,mCAAA,EAAsC,EAAE,CAAA,EAAA,CAAA,EAAM,GAAG,CAAA;AAAA,QACnE;AACA,QAAA,IAAI,QAAA,KAAa,WAAW,MAAM,GAAA;AAClC,QAAA,aAAA,CAAc,SAAS,CAAA;AAAA,MAC3B,CAAA,SAAE;AACE,QAAA,IAAI,CAAC,SAAA,EAAW,WAAA,CAAY,IAAI,CAAA;AAAA,MACpC;AAAA,IACJ,CAAA,GAAG;AAEH,IAAA,OAAO,MAAM;AACT,MAAA,SAAA,GAAY,IAAA;AAAA,IAChB,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,EAAA,EAAI,IAAI,CAAC,CAAA;AAEb,EAAA,SAAA,CAAU,MAAM;AACZ,IAAA,IAAI,SAAS,QAAA,EAAU;AACnB,MAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,qBAAA,EAAwB,EAAE,CAAA,cAAA,EAAiB,UAAU,CAAA,CAAA,CAAG,CAAA;AAAA,IACxE;AAAA,EACJ,GAAG,CAAC,KAAA,EAAO,EAAA,EAAI,UAAA,EAAY,QAAQ,CAAC,CAAA;AAEpC,EAAA,OAAO,EAAE,YAAY,QAAA,EAAS;AAClC;;;AC/GA,IAAM,MAAA,GAAS,cAAA;AACf,IAAM,SAAA,uBAAgB,GAAA,EAAY;AAE3B,SAAS,aAAA,CACZ,YAAA,EACA,UAAA,EACA,UAAA,EACA,OAAA,EACM;AACN,EAAA,OAAO,CAAA,EAAG,MAAM,CAAA,EAAG,YAAY,IAAI,UAAU,CAAA,CAAA,EAAI,UAAU,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA;AAC1E;AAEO,SAAS,QAAQ,GAAA,EAAsB;AAC1C,EAAA,IAAI,SAAA,CAAU,GAAA,CAAI,GAAG,CAAA,EAAG,OAAO,IAAA;AAC/B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,KAAA;AAC1C,EAAA,IAAI;AACA,IAAA,OAAO,cAAA,CAAe,OAAA,CAAQ,GAAG,CAAA,KAAM,GAAA;AAAA,EAC3C,CAAA,CAAA,MAAQ;AACJ,IAAA,OAAO,KAAA;AAAA,EACX;AACJ;AAEO,SAAS,SAAS,GAAA,EAAmB;AACxC,EAAA,SAAA,CAAU,IAAI,GAAG,CAAA;AACjB,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AACnC,EAAA,IAAI;AACA,IAAA,cAAA,CAAe,OAAA,CAAQ,KAAK,GAAG,CAAA;AAAA,EACnC,CAAA,CAAA,MAAQ;AAAA,EAAC;AACb;ACjBA,IAAM,eAAA,GAAkB,kBAAA;AAIxB,SAAS,OAAA,GAAkB;AACvB,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,CAAC,CAAA;AAC9B,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,MAAA,CAAO,eAAA,EAAiB;AACzD,IAAA,MAAA,CAAO,gBAAgB,KAAK,CAAA;AAAA,EAChC,CAAA,MAAO;AACH,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK,KAAA,CAAM,CAAC,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAA,KAAW,GAAG,CAAA;AAAA,EACzE;AACA,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,EAAO,CAAC,MAAM,CAAA,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,SAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAE,KAAK,EAAE,CAAA;AAC5E;AAKA,SAAS,gBAAgB,UAAA,EAA4B;AACjD,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAC/B,IAAA,IAAI;AACA,MAAA,MAAM,MAAA,GAAS,cAAA,CAAe,OAAA,CAAQ,UAAU,CAAA;AAChD,MAAA,IAAI,QAAQ,OAAO,MAAA;AAAA,IACvB,CAAA,CAAA,MAAQ;AAAA,IAAC;AAAA,EACb;AACA,EAAA,MAAM,EAAA,GAAK,CAAA,KAAA,EAAQ,OAAA,EAAS,CAAA,CAAA;AAC5B,EAAA,IAAI,OAAO,WAAW,WAAA,EAAa;AAC/B,IAAA,IAAI;AACA,MAAA,cAAA,CAAe,OAAA,CAAQ,YAAY,EAAE,CAAA;AAAA,IACzC,CAAA,CAAA,MAAQ;AAAA,IAAC;AAAA,EACb;AACA,EAAA,OAAO,EAAA;AACX;AAOA,IAAM,YAAA,uBAAmB,GAAA,EAAoB;AAC7C,IAAI,cAAA,GAAiB,KAAA;AAErB,SAAS,UAAU,QAAA,EAA0B;AACzC,EAAA,MAAM,GAAA,GAAM,YAAA,CAAa,GAAA,CAAI,QAAQ,CAAA,IAAK,CAAA;AAC1C,EAAA,YAAA,CAAa,GAAA,CAAI,QAAA,EAAU,GAAA,GAAM,CAAC,CAAA;AAClC,EAAA,IAAI,CAAC,cAAA,EAAgB;AACjB,IAAA,cAAA,GAAiB,IAAA;AACjB,IAAA,OAAA,CAAQ,OAAA,EAAQ,CAAE,IAAA,CAAK,MAAM;AACzB,MAAA,YAAA,CAAa,KAAA,EAAM;AACnB,MAAA,cAAA,GAAiB,KAAA;AAAA,IACrB,CAAC,CAAA;AAAA,EACL;AACA,EAAA,OAAO,GAAA;AACX;AAIA,SAAS,uBAAuB,YAAA,EAA8B;AAC1D,EAAA,MAAM,OAAA,GAAWA,OAAc,KAAA,EAAM;AACrC,EAAA,MAAM,GAAA,GAAM,OAAO,EAAE,CAAA;AACrB,EAAA,IAAI,CAAC,IAAI,OAAA,EAAS;AACd,IAAA,MAAM,GAAA,GAAM,GAAG,eAAe,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,UAAA,EAAY,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA;AACxE,IAAA,GAAA,CAAI,OAAA,GAAU,gBAAgB,GAAG,CAAA;AAAA,EACrC;AACA,EAAA,OAAO,GAAA,CAAI,OAAA;AACf;AAIA,SAAS,4BAA4B,YAAA,EAA8B;AAC/D,EAAA,MAAM,OAAA,GAAU,OAAO,EAAE,CAAA;AACzB,EAAA,MAAM,GAAA,GAAM,OAAO,EAAE,CAAA;AACrB,EAAA,IAAI,OAAA,CAAQ,YAAY,EAAA,EAAI;AACxB,IAAA,OAAA,CAAQ,UAAU,SAAA,CAAU,CAAA,EAAG,YAAY,CAAA,CAAA,EAAI,UAAA,EAAY,CAAA,CAAE,CAAA;AAAA,EACjE;AACA,EAAA,IAAI,CAAC,IAAI,OAAA,EAAS;AACd,IAAA,MAAM,GAAA,GAAM,CAAA,EAAG,eAAe,CAAA,EAAG,YAAY,IAAI,UAAA,EAAY,CAAA,CAAA,EAAI,OAAA,CAAQ,OAAO,CAAA,CAAA;AAChF,IAAA,GAAA,CAAI,OAAA,GAAU,gBAAgB,GAAG,CAAA;AAAA,EACrC;AACA,EAAA,OAAO,GAAA,CAAI,OAAA;AACf;AAMO,IAAM,mBAAA,GACT,OAAQA,MAAAA,CAAc,KAAA,KAAU,aAC1B,sBAAA,GACA,2BAAA;;;AC1CH,SAAS,SAAS,OAAA,EAA+D;AACpF,EAAA,MAAM;AAAA,IACF,YAAA;AAAA,IACA,mBAAA;AAAA,IACA,YAAY,eAAA,GAAkB,IAAA;AAAA,IAC9B,OAAO,UAAA,GAAa,IAAA;AAAA,IACpB,mBAAA,GAAsB,sBAAA;AAAA,IACtB,cAAA,GAAiB,mBAAA;AAAA,IACjB,KAAA,GAAQ;AAAA,GACZ,GAAI,OAAA;AAEJ,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,MAAA;AACzC,EAAA,MAAM,kBAAA,GAAqB,YAAA,IAAgB,OAAA,GAAU,OAAA,CAAQ,UAAA,GAAa,MAAA;AAE1E,EAAA,MAAM,EAAE,IAAA,EAAM,UAAA,EAAY,kBAAA,KAAuB,gBAAA,EAAiB;AAGlE,EAAA,MAAM,qBAAqB,kBAAA,IAAsB,kBAAA;AACjD,EAAA,MAAM,iBAAiB,CAAC,UAAA;AAExB,EAAA,MAAM,cAAA,GAAiB,oBAAoB,YAAY,CAAA;AACvD,EAAA,MAAM,aAAa,mBAAA,IAAuB,cAAA;AAE1C,EAAA,MAAM,YAAA,GAAeC,OAA2B,IAAI,CAAA;AACpD,EAAA,MAAM,cAAA,GAAiBA,OAAO,KAAK,CAAA;AAGnC,EAAAC,UAAU,MAAM;AACZ,IAAA,IAAI,cAAA,IAAkB,CAAC,kBAAA,IAAsB,KAAA,EAAO;AAChD,MAAA,OAAA,CAAQ,IAAA;AAAA,QACJ,gFACkB,YAAY,CAAA,2CAAA;AAAA,OAClC;AAAA,IACJ;AAAA,EACJ,GAAG,CAAC,cAAA,EAAgB,kBAAA,EAAoB,YAAA,EAAc,KAAK,CAAC,CAAA;AAE5D,EAAA,MAAM,UAAA,GAAaC,OAAAA;AAAA,IACf,OAAO;AAAA,MACH,aAAA,EAAe,YAAA;AAAA,MACf,GAAI,UAAA,GAAa,EAAE,WAAA,EAAa,UAAA,KAAe,EAAC;AAAA,MAChD,qBAAA,EAAuB,UAAA;AAAA,MACvB,GAAI,kBAAA,GAAqB,EAAE,WAAA,EAAa,kBAAA,KAAuB;AAAC,KACpE,CAAA;AAAA,IACA,CAAC,YAAA,EAAc,UAAA,EAAY,UAAA,EAAY,kBAAkB;AAAA,GAC7D;AAKA,EAAA,MAAM,aAAA,GAAgB,cAAc,kBAAA,IAAsB,UAAA;AAE1D,EAAAD,UAAU,MAAM;AACZ,IAAA,IAAI,CAAC,eAAA,EAAiB;AAEtB,IAAA,cAAA,CAAe,OAAA,GAAU,KAAA;AAEzB,IAAA,MAAM,UAAU,UAAA,EAAW;AAC3B,IAAA,MAAM,SAAA,GAAY,aAAA,CAAc,YAAA,EAAc,aAAA,EAAe,YAAY,OAAO,CAAA;AAEhF,IAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACpB,MAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AACzB,MAAA;AAAA,IACJ;AAEA,IAAA,MAAM,KAAK,YAAA,CAAa,OAAA;AACxB,IAAA,IAAI,CAAC,EAAA,EAAI;AAGT,IAAA,IAAI,OAAO,yBAAyB,WAAA,EAAa;AAC7C,MAAA,IAAI,CAAC,eAAe,OAAA,EAAS;AACzB,QAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AACzB,QAAA,QAAA,CAAS,SAAS,CAAA;AAClB,QAAA,UAAA,CAAW,IAAA,EAAM,qBAAqB,UAAU,CAAA;AAChD,QAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,GAAA,CAAI,CAAA,sCAAA,EAAyC,YAAY,CAAA,CAAA,CAAG,CAAA;AAAA,MACnF;AACA,MAAA;AAAA,IACJ;AAEA,IAAA,IAAI,KAAA,GAA8C,IAAA;AAElD,IAAA,MAAM,WAAW,IAAI,oBAAA;AAAA,MACjB,CAAC,CAAC,KAAK,CAAA,KAAM;AACT,QAAA,IAAI,CAAC,KAAA,IAAS,cAAA,CAAe,OAAA,EAAS;AAEtC,QAAA,IAAI,MAAM,cAAA,EAAgB;AACtB,UAAA,KAAA,GAAQ,WAAW,MAAM;AACrB,YAAA,IAAI,eAAe,OAAA,EAAS;AAC5B,YAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AACzB,YAAA,QAAA,CAAS,SAAS,CAAA;AAClB,YAAA,UAAA,CAAW,IAAA,EAAM,qBAAqB,UAAU,CAAA;AAChD,YAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,GAAA,CAAI,CAAA,8BAAA,EAAiC,YAAY,CAAA,CAAA,CAAG,CAAA;AACvE,YAAA,QAAA,CAAS,UAAA,EAAW;AAAA,UACxB,GAAG,GAAG,CAAA;AAAA,QACV,WAAW,KAAA,EAAO;AACd,UAAA,YAAA,CAAa,KAAK,CAAA;AAClB,UAAA,KAAA,GAAQ,IAAA;AAAA,QACZ;AAAA,MACJ,CAAA;AAAA,MACA,EAAE,WAAW,GAAA;AAAI,KACrB;AAEA,IAAA,QAAA,CAAS,QAAQ,EAAE,CAAA;AAEnB,IAAA,OAAO,MAAM;AACT,MAAA,QAAA,CAAS,UAAA,EAAW;AACpB,MAAA,IAAI,KAAA,eAAoB,KAAK,CAAA;AAAA,IACjC,CAAA;AAAA,EACJ,CAAA,EAAG;AAAA,IACC,eAAA;AAAA,IACA,YAAA;AAAA,IACA,aAAA;AAAA,IACA,UAAA;AAAA,IACA,IAAA;AAAA,IACA,mBAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACH,CAAA;AAID,EAAA,MAAM,WAAA,GAAc,WAAA;AAAA,IAChB,CAAC,CAAA,KAAa;AACV,MAAA,IAAI,CAAC,UAAA,EAAY;AAEjB,MAAA,MAAM,IAAA,GAAO,gBAAA,CAAiB,CAAA,CAAE,MAAqB,CAAA;AACrD,MAAA,IAAI,CAAC,IAAA,EAAM;AAEX,MAAA,UAAA,CAAW,MAAM,cAAA,EAAgB;AAAA,QAC7B,GAAG,UAAA;AAAA,QACH,GAAG;AAAA,OACN,CAAA;AACD,MAAA,IAAI,KAAA,EAAO;AACP,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,4BAAA,EAA+B,YAAY,CAAA,CAAA,CAAA,EAAK,IAAI,CAAA;AAAA,MACpE;AAAA,IACJ,CAAA;AAAA,IACA,CAAC,UAAA,EAAY,IAAA,EAAM,cAAA,EAAgB,UAAA,EAAY,cAAc,KAAK;AAAA,GACtE;AAEA,EAAAA,UAAU,MAAM;AACZ,IAAA,MAAM,KAAK,YAAA,CAAa,OAAA;AACxB,IAAA,IAAI,CAAC,EAAA,IAAM,CAAC,UAAA,EAAY;AAExB,IAAA,EAAA,CAAG,gBAAA,CAAiB,SAAS,WAAW,CAAA;AACxC,IAAA,OAAO,MAAM;AACT,MAAA,EAAA,CAAG,mBAAA,CAAoB,SAAS,WAAW,CAAA;AAAA,IAC/C,CAAA;AAAA,EACJ,CAAA,EAAG,CAAC,WAAA,EAAa,UAAU,CAAC,CAAA;AAE5B,EAAA,OAAO,YAAA;AACX;;;AChLO,SAAS,UAAA,CAAW;AAAA,EACvB,EAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,KAAA;AAAA,EACA,mBAAA;AAAA,EACA,QAAA,GAAW,SAAA;AAAA,EACX,KAAA,GAAQ;AACZ,CAAA,EAAoB;AAGhB,EAAA,MAAM,EAAE,UAAA,EAAY,MAAA,EAAQ,QAAA,EAAS,GAAI,cAAc,EAAA,EAAI,EAAE,QAAA,EAAU,KAAA,EAAO,CAAA;AAG9E,EAAA,MAAM,UAAA,GACF,MAAA,KAAW,SAAA,IAAa,MAAA,IAAU,WAAW,MAAA,GAAS,SAAA;AAE1D,EAAA,IAAI,KAAA,IAAS,WAAW,UAAA,EAAY;AAChC,IAAA,OAAA,CAAQ,IAAA;AAAA,MACJ,CAAA,0BAAA,EAA6B,MAAM,CAAA,kBAAA,EAAqB,EAAE,CAAA,0BAAA;AAAA,KAC9D;AAAA,EACJ;AAIA,EAAA,MAAM,WAAW,QAAA,CAAS;AAAA,IACtB,YAAA,EAAc,EAAA;AAAA,IACd,UAAA;AAAA,IACA,mBAAA;AAAA,IACA,UAAA,EAAY,QAAA,GAAY,KAAA,EAAO,UAAA,KAAe,KAAA,GAAS,KAAA;AAAA,IACvD,KAAA,EAAO,OAAO,YAAA,KAAiB,KAAA;AAAA,IAC/B,qBAAqB,KAAA,EAAO,mBAAA;AAAA,IAC5B,gBAAgB,KAAA,EAAO,cAAA;AAAA,IACvB;AAAA,GACH,CAAA;AAID,EAAA,MAAM,OAAA,GACF,eAAe,SAAA,IAAa,EAAE,cAAc,QAAA,CAAA,GACtC,OAAA,GACA,SAAS,UAAU,CAAA;AAE7B,EAAA,uBACIF,MAAAA,CAAA,aAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACG,GAAA,EAAK,QAAA;AAAA,MACL,wBAAA,EAAwB,EAAA;AAAA,MACxB,qBAAA,EAAqB,UAAA;AAAA,MACrB,KAAA,EAAO;AAAA,QACH,OAAA,EAAS,OAAA;AAAA,QACT,MAAA,EAAQ,CAAA;AAAA,QACR,OAAA,EAAS,CAAA;AAAA,QACT,OAAA,EAAS,WAAW,CAAA,GAAI,CAAA;AAAA,QACxB,UAAA,EAAY,WAAW,uBAAA,GAA0B;AAAA;AACrD,KAAA;AAAA,IAEC;AAAA,GACL;AAER;AC3EO,SAAS,KAAA,CAAM,EAAE,QAAA,EAAU,GAAG,cAAa,EAAe;AAC7D,EAAA,MAAM,QAAA,GAAW,SAAS,YAAY,CAAA;AAEtC,EAAA,uBACIA,MAAAA,CAAA,aAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACG,GAAA,EAAK,QAAA;AAAA,MACL,qBAAmB,YAAA,CAAa,YAAA;AAAA,MAChC,qBAAA,EAAqB,aAAa,UAAA,IAAc,iBAAA;AAAA,MAChD,KAAA,EAAO,EAAE,OAAA,EAAS,UAAA;AAAW,KAAA;AAAA,IAE5B;AAAA,GACL;AAER;ACTO,SAAS,gBAAA,GAA2C;AACvD,EAAA,MAAM,EAAE,IAAA,EAAM,UAAA,EAAW,GAAI,gBAAA,EAAiB;AAE9C,EAAA,MAAM,OAAA,GAAUI,WAAAA;AAAA,IACZ,CAAC,KAAA,EAAe,UAAA,GAAsC,EAAC,KAAM;AACzD,MAAA,UAAA,CAAW,MAAM,KAAA,EAAO;AAAA,QACpB,GAAI,UAAA,GAAa,EAAE,WAAA,EAAa,UAAA,KAAe,EAAC;AAAA,QAChD,GAAG;AAAA,OACN,CAAA;AAAA,IACL,CAAA;AAAA,IACA,CAAC,MAAM,UAAU;AAAA,GACrB;AAEA,EAAA,OAAO,EAAE,OAAA,EAAQ;AACrB;AChBO,SAAS,wBAAwB,YAAA,EAAsB;AAC1D,EAAA,MAAM,GAAA,GAAMC,cAA6B,IAAI,CAAA;AAE7C,EAAA,SAAS,kBAAA,CAAmB;AAAA,IACxB,KAAA;AAAA,IACA;AAAA,GACJ,EAGG;AACC,IAAA,OAAOL,OAAM,aAAA,CAAc,GAAA,CAAI,UAAU,EAAE,KAAA,IAAS,QAAQ,CAAA;AAAA,EAChE;AAEA,EAAA,SAAS,aAAA,GAAwB;AAC7B,IAAA,MAAM,CAAA,GAAIM,WAAW,GAAG,CAAA;AACxB,IAAA,IAAI,MAAM,IAAA,EAAM;AACZ,MAAA,MAAM,IAAI,KAAA;AAAA,QACN,iEAAiE,YAAY,CAAA,CAAA;AAAA,OACjF;AAAA,IACJ;AACA,IAAA,OAAO,CAAA;AAAA,EACX;AAEA,EAAA,OAAO,EAAE,oBAAoB,aAAA,EAAc;AAC/C","file":"index.mjs","sourcesContent":["\"use client\";\n\nimport React, { createContext, useContext, useMemo } from \"react\";\n\nexport interface ProbatContextValue {\n host: string;\n customerId?: string;\n bootstrap: Record<string, string>;\n}\n\nconst ProbatContext = createContext<ProbatContextValue | null>(null);\n\nconst DEFAULT_HOST = \"https://gushi.onrender.com\";\n\nexport interface ProbatProviderProps {\n /** Your end-user's ID. When provided, used as the distinct_id for variant\n * assignment (consistent across devices) and attached to all events. */\n customerId?: string;\n /** Base URL for the Probat API. Defaults to https://gushi.onrender.com */\n host?: string;\n /**\n * Bootstrap assignments to avoid flash on first render.\n * Map of experiment id → variant key.\n * e.g. { \"cta-copy-test\": \"ai_v1\" }\n */\n bootstrap?: Record<string, string>;\n children: React.ReactNode;\n}\n\nexport function ProbatProvider({\n customerId,\n host = DEFAULT_HOST,\n bootstrap,\n children,\n}: ProbatProviderProps) {\n const value = useMemo<ProbatContextValue>(\n () => ({\n host: host.replace(/\\/$/, \"\"),\n customerId,\n bootstrap: bootstrap ?? {},\n }),\n [customerId, host, bootstrap]\n );\n\n return (\n <ProbatContext.Provider value={value}>\n {children}\n </ProbatContext.Provider>\n );\n}\n\nexport function useProbatContext(): ProbatContextValue {\n const ctx = useContext(ProbatContext);\n if (!ctx) {\n throw new Error(\n \"useProbatContext must be used within <ProbatProviderClient>. Wrap your app with <ProbatProviderClient>.\"\n );\n }\n return ctx;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { ProbatProvider } from \"../context/ProbatContext\";\nimport type { ProbatProviderProps } from \"../context/ProbatContext\";\n\n/**\n * Client-only provider for Next.js App Router.\n * Import this in your layout/providers file.\n *\n * @example\n * ```tsx\n * // app/providers.tsx\n * \"use client\";\n * import { ProbatProviderClient } from \"@probat/react\";\n *\n * export function Providers({ children }) {\n * return (\n * <ProbatProviderClient customerId={user.id}>\n * {children}\n * </ProbatProviderClient>\n * );\n * }\n * ```\n */\nexport function ProbatProviderClient(props: ProbatProviderProps) {\n return React.createElement(ProbatProvider, props);\n}\n\nexport type { ProbatProviderProps };\n","/**\n * Detect if the code is running on localhost (development environment).\n * Returns \"dev\" for localhost, \"prod\" for production.\n */\nexport function detectEnvironment(): \"dev\" | \"prod\" {\n if (typeof window === \"undefined\") {\n return \"prod\"; // Server-side, default to prod\n }\n\n const hostname = window.location.hostname;\n\n // Check for localhost, 127.0.0.1, or local IP addresses\n if (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"0.0.0.0\" ||\n hostname.startsWith(\"192.168.\") ||\n hostname.startsWith(\"10.\") ||\n hostname.startsWith(\"172.16.\") ||\n hostname.startsWith(\"172.17.\") ||\n hostname.startsWith(\"172.18.\") ||\n hostname.startsWith(\"172.19.\") ||\n hostname.startsWith(\"172.20.\") ||\n hostname.startsWith(\"172.21.\") ||\n hostname.startsWith(\"172.22.\") ||\n hostname.startsWith(\"172.23.\") ||\n hostname.startsWith(\"172.24.\") ||\n hostname.startsWith(\"172.25.\") ||\n hostname.startsWith(\"172.26.\") ||\n hostname.startsWith(\"172.27.\") ||\n hostname.startsWith(\"172.28.\") ||\n hostname.startsWith(\"172.29.\") ||\n hostname.startsWith(\"172.30.\") ||\n hostname.startsWith(\"172.31.\")\n ) {\n return \"dev\";\n }\n\n return \"prod\";\n}\n\n","/**\n * Event context helpers: distinct_id, session_id, page info.\n * All browser-safe — no-ops when window is unavailable.\n */\n\nconst DISTINCT_ID_KEY = \"probat:distinct_id\";\nconst SESSION_ID_KEY = \"probat:session_id\";\n\nlet cachedDistinctId: string | null = null;\nlet cachedSessionId: string | null = null;\n\nfunction generateId(): string {\n // crypto.randomUUID where available, else fallback\n if (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n return crypto.randomUUID();\n }\n // fallback: random hex\n const bytes = new Uint8Array(16);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nexport function getDistinctId(): string {\n if (cachedDistinctId) return cachedDistinctId;\n if (typeof window === \"undefined\") return \"server\";\n try {\n const stored = localStorage.getItem(DISTINCT_ID_KEY);\n if (stored) {\n cachedDistinctId = stored;\n return stored;\n }\n } catch {}\n const id = `anon_${generateId()}`;\n cachedDistinctId = id;\n try {\n localStorage.setItem(DISTINCT_ID_KEY, id);\n } catch {}\n return id;\n}\n\nexport function getSessionId(): string {\n if (cachedSessionId) return cachedSessionId;\n if (typeof window === \"undefined\") return \"server\";\n try {\n const stored = sessionStorage.getItem(SESSION_ID_KEY);\n if (stored) {\n cachedSessionId = stored;\n return stored;\n }\n } catch {}\n const id = `sess_${generateId()}`;\n cachedSessionId = id;\n try {\n sessionStorage.setItem(SESSION_ID_KEY, id);\n } catch {}\n return id;\n}\n\nexport function getPageKey(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.pathname + window.location.search;\n}\n\nexport function getPageUrl(): string {\n if (typeof window === \"undefined\") return \"\";\n return window.location.href;\n}\n\nexport function getReferrer(): string {\n if (typeof document === \"undefined\") return \"\";\n return document.referrer;\n}\n\nexport interface EventContext {\n distinct_id: string;\n session_id: string;\n $page_url: string;\n $pathname: string;\n $referrer: string;\n}\n\nexport function buildEventContext(): EventContext {\n return {\n distinct_id: getDistinctId(),\n session_id: getSessionId(),\n $page_url: getPageUrl(),\n $pathname: typeof window !== \"undefined\" ? window.location.pathname : \"\",\n $referrer: getReferrer(),\n };\n}\n","import { detectEnvironment } from \"./environment\";\nimport { buildEventContext } from \"./eventContext\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface DecisionResponse {\n variant_key: string;\n}\n\nexport interface MetricPayload {\n event: string;\n environment: \"dev\" | \"prod\";\n properties: Record<string, unknown>;\n}\n\n// ── Assignment fetching ────────────────────────────────────────────────────\n\nconst pendingDecisions = new Map<string, Promise<string>>();\n\n/**\n * Fetch the variant assignment for an experiment.\n * Returns the variant key string (e.g. \"control\", \"ai_v1\").\n * Deduplicates concurrent calls for the same experiment.\n */\nexport async function fetchDecision(\n host: string,\n experimentId: string,\n distinctId: string\n): Promise<string> {\n const existing = pendingDecisions.get(experimentId);\n if (existing) return existing;\n\n const promise = (async () => {\n try {\n const url = `${host.replace(/\\/$/, \"\")}/experiment/decide`;\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n credentials: \"include\",\n body: JSON.stringify({\n experiment_id: experimentId,\n distinct_id: distinctId,\n }),\n });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const data = (await res.json()) as DecisionResponse;\n return data.variant_key || \"control\";\n } finally {\n pendingDecisions.delete(experimentId);\n }\n })();\n\n pendingDecisions.set(experimentId, promise);\n return promise;\n}\n\n// ── Metric sending ─────────────────────────────────────────────────────────\n\n/**\n * Fire-and-forget metric send. Never throws.\n */\nexport function sendMetric(\n host: string,\n event: string,\n properties: Record<string, unknown>\n): void {\n if (typeof window === \"undefined\") return;\n\n const ctx = buildEventContext();\n const payload: MetricPayload = {\n event,\n environment: detectEnvironment(),\n properties: {\n ...ctx,\n source: \"react-sdk\",\n captured_at: new Date().toISOString(),\n ...properties,\n },\n };\n\n try {\n const url = `${host.replace(/\\/$/, \"\")}/experiment/metrics`;\n fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify(payload),\n }).catch(() => {});\n } catch {\n // silently drop\n }\n}\n\n// ── Click metadata extraction ──────────────────────────────────────────────\n\nexport interface ClickMeta {\n click_target_tag: string;\n click_target_text?: string;\n click_target_id?: string;\n click_is_primary: boolean;\n}\n\n/**\n * Given a click event inside an experiment boundary, extract metadata.\n * Prioritizes elements with data-probat-click=\"primary\",\n * then falls back to button/a/role=button.\n */\nexport function extractClickMeta(target: EventTarget | null): ClickMeta | null {\n if (!target || !(target instanceof HTMLElement)) return null;\n\n // Priority 1: explicit primary marker\n const primary = target.closest('[data-probat-click=\"primary\"]');\n if (primary) return buildMeta(primary as HTMLElement, true);\n\n // Priority 2: interactive elements\n const interactive = target.closest('button, a, [role=\"button\"]');\n if (interactive) return buildMeta(interactive as HTMLElement, false);\n\n return null;\n}\n\nfunction buildMeta(el: HTMLElement, isPrimary: boolean): ClickMeta {\n const meta: ClickMeta = {\n click_target_tag: el.tagName,\n click_is_primary: isPrimary,\n };\n if (el.id) meta.click_target_id = el.id;\n const text = el.textContent?.trim();\n if (text) meta.click_target_text = text.slice(0, 120);\n return meta;\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { fetchDecision } from \"../utils/api\";\nimport { getDistinctId } from \"../utils/eventContext\";\n\n// ── localStorage assignment cache ──────────────────────────────────────────\n\nconst ASSIGNMENT_PREFIX = \"probat:assignment:\";\n\ninterface StoredAssignment {\n variantKey: string;\n ts: number;\n}\n\nexport function readAssignment(id: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n const raw = localStorage.getItem(ASSIGNMENT_PREFIX + id);\n if (!raw) return null;\n const parsed: StoredAssignment = JSON.parse(raw);\n return parsed.variantKey ?? null;\n } catch {\n return null;\n }\n}\n\nexport function writeAssignment(id: string, variantKey: string): void {\n if (typeof window === \"undefined\") return;\n try {\n const entry: StoredAssignment = { variantKey, ts: Date.now() };\n localStorage.setItem(ASSIGNMENT_PREFIX + id, JSON.stringify(entry));\n } catch {}\n}\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface UseExperimentOptions {\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions to console */\n debug?: boolean;\n}\n\nexport interface UseExperimentReturn {\n /** The resolved variant key (e.g. \"control\", \"ai_v1\") */\n variantKey: string;\n /** Whether the assignment has been resolved (bootstrap, cache, or fetch) */\n resolved: boolean;\n}\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Resolves the variant assignment for an experiment.\n * No tracking — use `useTrack` or `<Track>` to observe impressions/clicks.\n *\n * Priority: bootstrap > localStorage cache > fetchDecision.\n */\nexport function useExperiment(\n id: string,\n options: UseExperimentOptions = {}\n): UseExperimentReturn {\n const { fallback = \"control\", debug = false } = options;\n const { host, bootstrap, customerId } = useProbatContext();\n\n const [variantKey, setVariantKey] = useState<string>(() => {\n if (bootstrap[id]) return bootstrap[id];\n return \"control\";\n });\n const [resolved, setResolved] = useState<boolean>(() => {\n return !!bootstrap[id];\n });\n\n useEffect(() => {\n if (bootstrap[id] || readAssignment(id)) {\n const key = bootstrap[id] ?? readAssignment(id) ?? \"control\";\n setVariantKey(key);\n setResolved(true);\n return;\n }\n\n let cancelled = false;\n\n (async () => {\n try {\n const distinctId = customerId ?? getDistinctId();\n const key = await fetchDecision(host, id, distinctId);\n if (cancelled) return;\n\n setVariantKey(key);\n writeAssignment(id, key);\n } catch (err) {\n if (cancelled) return;\n if (debug) {\n console.error(`[probat] fetchDecision failed for \"${id}\":`, err);\n }\n if (fallback === \"suspend\") throw err;\n setVariantKey(\"control\");\n } finally {\n if (!cancelled) setResolved(true);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [id, host]); // eslint-disable-line react-hooks/exhaustive-deps\n\n useEffect(() => {\n if (debug && resolved) {\n console.log(`[probat] Experiment \"${id}\" -> variant \"${variantKey}\"`);\n }\n }, [debug, id, variantKey, resolved]);\n\n return { variantKey, resolved };\n}\n","/**\n * Dedupe storage for experiment exposures.\n * Uses sessionStorage + in-memory Set fallback.\n * Key format: probat:seen:{id}:{variantKey}:{instanceId}:{pageKey}\n */\n\nconst PREFIX = \"probat:seen:\";\nconst memorySet = new Set<string>();\n\nexport function makeDedupeKey(\n experimentId: string,\n variantKey: string,\n instanceId: string,\n pageKey: string\n): string {\n return `${PREFIX}${experimentId}:${variantKey}:${instanceId}:${pageKey}`;\n}\n\nexport function hasSeen(key: string): boolean {\n if (memorySet.has(key)) return true;\n if (typeof window === \"undefined\") return false;\n try {\n return sessionStorage.getItem(key) === \"1\";\n } catch {\n return false;\n }\n}\n\nexport function markSeen(key: string): void {\n memorySet.add(key);\n if (typeof window === \"undefined\") return;\n try {\n sessionStorage.setItem(key, \"1\");\n } catch {}\n}\n\n/** Reset all dedupe state — useful for testing. */\nexport function resetDedupe(): void {\n memorySet.clear();\n}\n","/**\n * Stable auto-generated instance IDs for <Experiment />.\n *\n * Problem: a naïve useRef + module counter gives a different ID on every mount,\n * so StrictMode double-mount or unmount/remount changes the dedupe key.\n *\n * Solution:\n * 1. React 18+ → useId() is stable per fiber position.\n * 2. Fallback → sessionStorage-backed slot counter per (experimentId, pageKey).\n * 3. Both paths persist a mapping in sessionStorage:\n * probat:instance:{experimentId}:{pageKey}:{positionKey} → stableId\n * so the same position resolves to the same ID across mounts.\n */\n\nimport React, { useRef } from \"react\";\nimport { getPageKey } from \"./eventContext\";\n\nconst INSTANCE_PREFIX = \"probat:instance:\";\n\n// ── Helpers ────────────────────────────────────────────────────────────────\n\nfunction shortId(): string {\n const bytes = new Uint8Array(4);\n if (typeof crypto !== \"undefined\" && crypto.getRandomValues) {\n crypto.getRandomValues(bytes);\n } else {\n for (let i = 0; i < 4; i++) bytes[i] = Math.floor(Math.random() * 256);\n }\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n/**\n * Look up or create a stable instance ID in sessionStorage.\n */\nfunction resolveStableId(storageKey: string): string {\n if (typeof window !== \"undefined\") {\n try {\n const stored = sessionStorage.getItem(storageKey);\n if (stored) return stored;\n } catch {}\n }\n const id = `inst_${shortId()}`;\n if (typeof window !== \"undefined\") {\n try {\n sessionStorage.setItem(storageKey, id);\n } catch {}\n }\n return id;\n}\n\n// ── Fallback: render-wave slot counter ─────────────────────────────────────\n// Each synchronous render batch claims sequential slots per (experimentId,\n// pageKey). A microtask resets the counters so the next batch starts at 0,\n// giving the same component position the same slot across mounts.\n\nconst slotCounters = new Map<string, number>();\nlet resetScheduled = false;\n\nfunction claimSlot(groupKey: string): number {\n const idx = slotCounters.get(groupKey) ?? 0;\n slotCounters.set(groupKey, idx + 1);\n if (!resetScheduled) {\n resetScheduled = true;\n Promise.resolve().then(() => {\n slotCounters.clear();\n resetScheduled = false;\n });\n }\n return idx;\n}\n\n// ── Hook: React 18+ path (useId available) ─────────────────────────────────\n\nfunction useStableInstanceIdV18(experimentId: string): string {\n const reactId = (React as any).useId() as string;\n const ref = useRef(\"\");\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${reactId}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Hook: fallback path (no useId) ─────────────────────────────────────────\n\nfunction useStableInstanceIdFallback(experimentId: string): string {\n const slotRef = useRef(-1);\n const ref = useRef(\"\");\n if (slotRef.current === -1) {\n slotRef.current = claimSlot(`${experimentId}:${getPageKey()}`);\n }\n if (!ref.current) {\n const key = `${INSTANCE_PREFIX}${experimentId}:${getPageKey()}:${slotRef.current}`;\n ref.current = resolveStableId(key);\n }\n return ref.current;\n}\n\n// ── Exported hook ──────────────────────────────────────────────────────────\n// Selection is a module-level constant so the hook-call count never changes\n// between renders — safe for the rules of hooks.\n\nexport const useStableInstanceId: (experimentId: string) => string =\n typeof (React as any).useId === \"function\"\n ? useStableInstanceIdV18\n : useStableInstanceIdFallback;\n\n// ── Test utility ───────────────────────────────────────────────────────────\n\nexport function resetInstanceIdState(): void {\n slotCounters.clear();\n resetScheduled = false;\n}\n","\"use client\";\n\nimport { useEffect, useRef, useMemo, useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric, extractClickMeta } from \"../utils/api\";\nimport { getPageKey } from \"../utils/eventContext\";\nimport { makeDedupeKey, hasSeen, markSeen } from \"../utils/dedupeStorage\";\nimport { useStableInstanceId } from \"../utils/stableInstanceId\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\ninterface UseTrackBaseOptions {\n /** Experiment identifier */\n experimentId: string;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n click?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n /** Log events to console */\n debug?: boolean;\n}\n\n/** Explicit mode: pass the variant key directly */\nexport interface UseTrackExplicitOptions extends UseTrackBaseOptions {\n /** The variant key to attach to events */\n variantKey: string;\n customerId?: undefined;\n}\n\n/** Customer mode: backend resolves variant from assignment table */\nexport interface UseTrackCustomerOptions extends UseTrackBaseOptions {\n variantKey?: undefined;\n /** Customer ID to resolve variant server-side. Falls back to provider's customerId. */\n customerId?: string;\n}\n\nexport type UseTrackOptions = UseTrackExplicitOptions | UseTrackCustomerOptions;\n\n// ── Hook ───────────────────────────────────────────────────────────────────\n\n/**\n * Attaches impression and click tracking to a DOM element via a ref.\n *\n * Two modes:\n * - **Explicit**: pass `variantKey` directly — stamped on every event.\n * - **Customer**: omit `variantKey` — backend resolves it from the assignment table\n * using `(experimentId, customerId)`.\n *\n * @example\n * ```tsx\n * // Explicit mode\n * const trackRef = useTrack({ experimentId: \"pricing\", variantKey: \"ai_v1\" });\n *\n * // Customer mode (backend resolves variant)\n * const trackRef = useTrack({ experimentId: \"pricing\", customerId: \"user_123\" });\n * ```\n */\nexport function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement | null> {\n const {\n experimentId,\n componentInstanceId,\n impression: trackImpression = true,\n click: trackClick = true,\n impressionEventName = \"$experiment_exposure\",\n clickEventName = \"$experiment_click\",\n debug = false,\n } = options;\n\n const variantKey = options.variantKey ?? undefined;\n const explicitCustomerId = \"customerId\" in options ? options.customerId : undefined;\n\n const { host, customerId: providerCustomerId } = useProbatContext();\n\n // In customer mode, use explicit customerId or fall back to provider's\n const resolvedCustomerId = explicitCustomerId ?? providerCustomerId;\n const isCustomerMode = !variantKey;\n\n const autoInstanceId = useStableInstanceId(experimentId);\n const instanceId = componentInstanceId ?? autoInstanceId;\n\n const containerRef = useRef<HTMLElement | null>(null);\n const impressionSent = useRef(false);\n\n // Runtime warning\n useEffect(() => {\n if (isCustomerMode && !resolvedCustomerId && debug) {\n console.warn(\n `[probat] useTrack called without variantKey and no customerId ` +\n `available for \"${experimentId}\". Events will have no variant attribution.`\n );\n }\n }, [isCustomerMode, resolvedCustomerId, experimentId, debug]);\n\n const eventProps = useMemo(\n () => ({\n experiment_id: experimentId,\n ...(variantKey ? { variant_key: variantKey } : {}),\n component_instance_id: instanceId,\n ...(resolvedCustomerId ? { distinct_id: resolvedCustomerId } : {}),\n }),\n [experimentId, variantKey, instanceId, resolvedCustomerId]\n );\n\n // ── Impression tracking via IntersectionObserver ────────────────────────\n\n // In customer mode, use customerId for dedupe instead of variantKey\n const dedupeVariant = variantKey ?? resolvedCustomerId ?? \"__anon__\";\n\n useEffect(() => {\n if (!trackImpression) return;\n\n impressionSent.current = false;\n\n const pageKey = getPageKey();\n const dedupeKey = makeDedupeKey(experimentId, dedupeVariant, instanceId, pageKey);\n\n if (hasSeen(dedupeKey)) {\n impressionSent.current = true;\n return;\n }\n\n const el = containerRef.current;\n if (!el) return;\n\n // Fallback: no IntersectionObserver (SSR, old browser)\n if (typeof IntersectionObserver === \"undefined\") {\n if (!impressionSent.current) {\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps);\n if (debug) console.log(`[probat] Impression sent (no IO) for \"${experimentId}\"`);\n }\n return;\n }\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry || impressionSent.current) return;\n\n if (entry.isIntersecting) {\n timer = setTimeout(() => {\n if (impressionSent.current) return;\n impressionSent.current = true;\n markSeen(dedupeKey);\n sendMetric(host, impressionEventName, eventProps);\n if (debug) console.log(`[probat] Impression sent for \"${experimentId}\"`);\n observer.disconnect();\n }, 250);\n } else if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n },\n { threshold: 0.5 }\n );\n\n observer.observe(el);\n\n return () => {\n observer.disconnect();\n if (timer) clearTimeout(timer);\n };\n }, [\n trackImpression,\n experimentId,\n dedupeVariant,\n instanceId,\n host,\n impressionEventName,\n eventProps,\n debug,\n ]);\n\n // ── Click tracking ─────────────────────────────────────────────────────\n\n const handleClick = useCallback(\n (e: Event) => {\n if (!trackClick) return;\n\n const meta = extractClickMeta(e.target as EventTarget);\n if (!meta) return;\n\n sendMetric(host, clickEventName, {\n ...eventProps,\n ...meta,\n });\n if (debug) {\n console.log(`[probat] Click tracked for \"${experimentId}\"`, meta);\n }\n },\n [trackClick, host, clickEventName, eventProps, experimentId, debug]\n );\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el || !trackClick) return;\n\n el.addEventListener(\"click\", handleClick);\n return () => {\n el.removeEventListener(\"click\", handleClick);\n };\n }, [handleClick, trackClick]);\n\n return containerRef;\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useExperiment } from \"../hooks/useExperiment\";\nimport { useTrack } from \"../hooks/useTrack\";\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport interface ExperimentTrackOptions {\n /** Auto-track impressions (default true) */\n impression?: boolean;\n /** Auto-track clicks (default true) */\n primaryClick?: boolean;\n /** Custom impression event name (default \"$experiment_exposure\") */\n impressionEventName?: string;\n /** Custom click event name (default \"$experiment_click\") */\n clickEventName?: string;\n}\n\nexport interface ExperimentProps {\n /** Experiment key / identifier */\n id: string;\n /** Control variant ReactNode */\n control: React.ReactNode;\n /** Named variant ReactNodes, keyed by variant key (e.g. { ai_v1: <MyVariant /> }) */\n variants: Record<string, React.ReactNode>;\n /** Tracking configuration */\n track?: ExperimentTrackOptions;\n /** Stable instance id when multiple instances of the same experiment exist on a page */\n componentInstanceId?: string;\n /** Behavior when assignment fetch fails: \"control\" (default) renders control, \"suspend\" throws */\n fallback?: \"control\" | \"suspend\";\n /** Log decisions + events to console */\n debug?: boolean;\n}\n\nexport function Experiment({\n id,\n control,\n variants,\n track,\n componentInstanceId,\n fallback = \"control\",\n debug = false,\n}: ExperimentProps) {\n // ── Assignment (decoupled) ──────────────────────────────────────────────\n\n const { variantKey: rawKey, resolved } = useExperiment(id, { fallback, debug });\n\n // Validate variant key against the ReactNode map\n const variantKey =\n rawKey === \"control\" || rawKey in variants ? rawKey : \"control\";\n\n if (debug && rawKey !== variantKey) {\n console.warn(\n `[probat] Unknown variant \"${rawKey}\" for experiment \"${id}\", falling back to control`\n );\n }\n\n // ── Tracking (decoupled) ────────────────────────────────────────────────\n\n const trackRef = useTrack({\n experimentId: id,\n variantKey,\n componentInstanceId,\n impression: resolved ? (track?.impression !== false) : false,\n click: track?.primaryClick !== false,\n impressionEventName: track?.impressionEventName,\n clickEventName: track?.clickEventName,\n debug,\n });\n\n // ── Render ─────────────────────────────────────────────────────────────\n\n const content =\n variantKey === \"control\" || !(variantKey in variants)\n ? control\n : variants[variantKey];\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-experiment={id}\n data-probat-variant={variantKey}\n style={{\n display: \"block\",\n margin: 0,\n padding: 0,\n opacity: resolved ? 1 : 0,\n transition: resolved ? \"opacity 0.15s ease-in\" : \"none\",\n }}\n >\n {content}\n </div>\n );\n}\n","\"use client\";\n\nimport React from \"react\";\nimport { useTrack, type UseTrackOptions } from \"../hooks/useTrack\";\n\nexport type TrackProps = UseTrackOptions & {\n children: React.ReactNode;\n};\n\n/**\n * Wrapper component that attaches impression and click tracking to its children.\n * Alternative to the `useTrack` hook when you prefer a component over a ref.\n *\n * @example\n * ```tsx\n * <Track experimentId=\"pricing\" variantKey=\"ai_v1\">\n * <PricingCard />\n * </Track>\n * ```\n */\nexport function Track({ children, ...trackOptions }: TrackProps) {\n const trackRef = useTrack(trackOptions);\n\n return (\n <div\n ref={trackRef as React.RefObject<HTMLDivElement>}\n data-probat-track={trackOptions.experimentId}\n data-probat-variant={trackOptions.variantKey ?? \"server-resolved\"}\n style={{ display: \"contents\" }}\n >\n {children}\n </div>\n );\n}\n","\"use client\";\n\nimport { useCallback } from \"react\";\nimport { useProbatContext } from \"../context/ProbatContext\";\nimport { sendMetric } from \"../utils/api\";\n\nexport interface UseProbatMetricsReturn {\n /**\n * Send a custom event with arbitrary properties.\n * Never throws — failures are silently dropped.\n *\n * @example\n * ```tsx\n * const { capture } = useProbatMetrics();\n * capture(\"purchase\", { revenue: 42, currency: \"USD\" });\n * ```\n */\n capture: (event: string, properties?: Record<string, unknown>) => void;\n}\n\n/**\n * Minimal metrics hook. Provides a single `capture(event, props)` function\n * that sends events to the Probat backend using the provider's host config.\n */\nexport function useProbatMetrics(): UseProbatMetricsReturn {\n const { host, customerId } = useProbatContext();\n\n const capture = useCallback(\n (event: string, properties: Record<string, unknown> = {}) => {\n sendMetric(host, event, {\n ...(customerId ? { distinct_id: customerId } : {}),\n ...properties,\n });\n },\n [host, customerId]\n );\n\n return { capture };\n}\n","\"use client\";\n\nimport React, { createContext, useContext, type ReactNode } from \"react\";\n\n/**\n * Factory that creates a typed context + hook pair for passing a variantKey\n * between components in different files without prop-drilling.\n *\n * @example\n * ```tsx\n * // experiment-context.ts\n * export const { ExperimentProvider, useVariantKey } = createExperimentContext(\"pricing-test\");\n *\n * // Parent.tsx\n * const { variantKey } = useExperiment(\"pricing-test\");\n * <ExperimentProvider value={variantKey}><Child /></ExperimentProvider>\n *\n * // Child.tsx (different file)\n * const variantKey = useVariantKey();\n * const trackRef = useTrack({ experimentId: \"pricing-test\", variantKey });\n * ```\n */\nexport function createExperimentContext(experimentId: string) {\n const Ctx = createContext<string | null>(null);\n\n function ExperimentProvider({\n value,\n children,\n }: {\n value: string;\n children: ReactNode;\n }) {\n return React.createElement(Ctx.Provider, { value }, children);\n }\n\n function useVariantKey(): string {\n const v = useContext(Ctx);\n if (v === null) {\n throw new Error(\n `useVariantKey() must be used inside <ExperimentProvider> for \"${experimentId}\"`\n );\n }\n return v;\n }\n\n return { ExperimentProvider, useVariantKey };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@probat/react",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "React SDK for Probat experimentation — CTR tracking, variant assignment, and more",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -38,6 +38,9 @@
38
38
  "sideEffects": false,
39
39
  "author": "Probat AI",
40
40
  "license": "MIT",
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
41
44
  "peerDependencies": {
42
45
  "react": ">=18.0.0"
43
46
  },
@@ -392,6 +392,257 @@ describe("useTrack event payload", () => {
392
392
  });
393
393
  });
394
394
 
395
+ // ── Customer mode ─────────────────────────────────────────────────────────
396
+
397
+ function CustomerTrackedContent(props: {
398
+ experimentId: string;
399
+ customerId?: string;
400
+ componentInstanceId?: string;
401
+ impression?: boolean;
402
+ click?: boolean;
403
+ children: React.ReactNode;
404
+ }) {
405
+ const { experimentId, customerId, componentInstanceId, impression, click, children } = props;
406
+ const trackRef = useTrack({
407
+ experimentId,
408
+ customerId,
409
+ componentInstanceId,
410
+ impression,
411
+ click,
412
+ });
413
+
414
+ return (
415
+ <div ref={trackRef as React.RefObject<HTMLDivElement>} data-testid="tracked">
416
+ {children}
417
+ </div>
418
+ );
419
+ }
420
+
421
+ describe("useTrack customer mode", () => {
422
+ it("sends impression without variant_key but with distinct_id + experiment_id", async () => {
423
+ const fetchMock = global.fetch as ReturnType<typeof vi.fn>;
424
+
425
+ render(
426
+ <CustomerTrackedContent experimentId="cust-exp" customerId="user_42">
427
+ <div>Content</div>
428
+ </CustomerTrackedContent>,
429
+ { wrapper }
430
+ );
431
+
432
+ await act(async () => {
433
+ await vi.runAllTimersAsync();
434
+ });
435
+
436
+ const observer = MockIntersectionObserver._instances[MockIntersectionObserver._instances.length - 1];
437
+ act(() => observer._trigger(true));
438
+ await act(async () => {
439
+ await vi.advanceTimersByTimeAsync(300);
440
+ });
441
+
442
+ const impressionCalls = fetchMock.mock.calls.filter((c: any[]) => {
443
+ if (typeof c[0] !== "string" || !c[0].includes("/experiment/metrics")) return false;
444
+ const body = JSON.parse(c[1]?.body || "{}");
445
+ return body.event === "$experiment_exposure";
446
+ });
447
+
448
+ expect(impressionCalls.length).toBe(1);
449
+ const payload = JSON.parse(impressionCalls[0][1].body);
450
+ expect(payload.properties.experiment_id).toBe("cust-exp");
451
+ expect(payload.properties.distinct_id).toBe("user_42");
452
+ expect(payload.properties).not.toHaveProperty("variant_key");
453
+ });
454
+
455
+ it("dedupes impressions using customerId in the dedupe key", async () => {
456
+ const fetchMock = global.fetch as ReturnType<typeof vi.fn>;
457
+
458
+ const { unmount } = render(
459
+ <CustomerTrackedContent experimentId="cust-dedupe" customerId="user_42" componentInstanceId="stable">
460
+ <div>Content</div>
461
+ </CustomerTrackedContent>,
462
+ { wrapper }
463
+ );
464
+
465
+ await act(async () => {
466
+ await vi.runAllTimersAsync();
467
+ });
468
+
469
+ const observer1 = MockIntersectionObserver._instances[MockIntersectionObserver._instances.length - 1];
470
+ act(() => observer1._trigger(true));
471
+ await act(async () => {
472
+ await vi.advanceTimersByTimeAsync(300);
473
+ });
474
+
475
+ const callsAfterFirst = fetchMock.mock.calls.filter((c: any[]) => {
476
+ if (typeof c[0] !== "string" || !c[0].includes("/experiment/metrics")) return false;
477
+ const body = JSON.parse(c[1]?.body || "{}");
478
+ return body.event === "$experiment_exposure";
479
+ });
480
+ expect(callsAfterFirst.length).toBe(1);
481
+
482
+ unmount();
483
+
484
+ render(
485
+ <CustomerTrackedContent experimentId="cust-dedupe" customerId="user_42" componentInstanceId="stable">
486
+ <div>Content</div>
487
+ </CustomerTrackedContent>,
488
+ { wrapper }
489
+ );
490
+
491
+ await act(async () => {
492
+ await vi.runAllTimersAsync();
493
+ });
494
+
495
+ const observer2 = MockIntersectionObserver._instances[MockIntersectionObserver._instances.length - 1];
496
+ act(() => observer2._trigger(true));
497
+ await act(async () => {
498
+ await vi.advanceTimersByTimeAsync(300);
499
+ });
500
+
501
+ const callsAfterSecond = fetchMock.mock.calls.filter((c: any[]) => {
502
+ if (typeof c[0] !== "string" || !c[0].includes("/experiment/metrics")) return false;
503
+ const body = JSON.parse(c[1]?.body || "{}");
504
+ return body.event === "$experiment_exposure";
505
+ });
506
+ // Should still be 1 -- deduped
507
+ expect(callsAfterSecond.length).toBe(1);
508
+ });
509
+
510
+ it("tracks clicks in customer mode", async () => {
511
+ const fetchMock = global.fetch as ReturnType<typeof vi.fn>;
512
+
513
+ render(
514
+ <CustomerTrackedContent experimentId="cust-click" customerId="user_42">
515
+ <button data-testid="btn">Buy</button>
516
+ </CustomerTrackedContent>,
517
+ { wrapper }
518
+ );
519
+
520
+ await act(async () => {
521
+ await vi.runAllTimersAsync();
522
+ });
523
+
524
+ fireEvent.click(screen.getByTestId("btn"));
525
+
526
+ await act(async () => {
527
+ await vi.runAllTimersAsync();
528
+ });
529
+
530
+ const clickCalls = fetchMock.mock.calls.filter((c: any[]) => {
531
+ if (typeof c[0] !== "string" || !c[0].includes("/experiment/metrics")) return false;
532
+ const body = JSON.parse(c[1]?.body || "{}");
533
+ return body.event === "$experiment_click";
534
+ });
535
+
536
+ expect(clickCalls.length).toBe(1);
537
+ const payload = JSON.parse(clickCalls[0][1].body);
538
+ expect(payload.properties.experiment_id).toBe("cust-click");
539
+ expect(payload.properties.distinct_id).toBe("user_42");
540
+ expect(payload.properties).not.toHaveProperty("variant_key");
541
+ });
542
+
543
+ it("falls back to provider customerId when customerId option is omitted", async () => {
544
+ const fetchMock = global.fetch as ReturnType<typeof vi.fn>;
545
+
546
+ render(
547
+ <CustomerTrackedContent experimentId="cust-fallback">
548
+ <div>Content</div>
549
+ </CustomerTrackedContent>,
550
+ { wrapper }
551
+ );
552
+
553
+ await act(async () => {
554
+ await vi.runAllTimersAsync();
555
+ });
556
+
557
+ const observer = MockIntersectionObserver._instances[MockIntersectionObserver._instances.length - 1];
558
+ act(() => observer._trigger(true));
559
+ await act(async () => {
560
+ await vi.advanceTimersByTimeAsync(300);
561
+ });
562
+
563
+ const impressionCalls = fetchMock.mock.calls.filter((c: any[]) => {
564
+ if (typeof c[0] !== "string" || !c[0].includes("/experiment/metrics")) return false;
565
+ const body = JSON.parse(c[1]?.body || "{}");
566
+ return body.event === "$experiment_exposure";
567
+ });
568
+
569
+ expect(impressionCalls.length).toBe(1);
570
+ const payload = JSON.parse(impressionCalls[0][1].body);
571
+ // Falls back to provider's customerId ("test-customer-id" from wrapper)
572
+ expect(payload.properties.distinct_id).toBe("test-customer-id");
573
+ expect(payload.properties).not.toHaveProperty("variant_key");
574
+ });
575
+
576
+ it("warns when neither variantKey nor customerId is available", async () => {
577
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
578
+
579
+ function NoCustomerProvider({ children }: { children: React.ReactNode }) {
580
+ return (
581
+ <ProbatProviderClient host="https://api.test.com">
582
+ {children}
583
+ </ProbatProviderClient>
584
+ );
585
+ }
586
+
587
+ render(
588
+ <NoCustomerProvider>
589
+ <CustomerTrackedContent experimentId="no-id-exp" impression={true}>
590
+ <div>Content</div>
591
+ </CustomerTrackedContent>
592
+ </NoCustomerProvider>
593
+ );
594
+
595
+ await act(async () => {
596
+ await vi.runAllTimersAsync();
597
+ });
598
+
599
+ // debug is false by default, so the warning should not fire
600
+ expect(warnSpy).not.toHaveBeenCalled();
601
+
602
+ warnSpy.mockRestore();
603
+ });
604
+
605
+ it("warns when debug=true and no customerId available", async () => {
606
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
607
+
608
+ function NoCustomerProvider({ children }: { children: React.ReactNode }) {
609
+ return (
610
+ <ProbatProviderClient host="https://api.test.com">
611
+ {children}
612
+ </ProbatProviderClient>
613
+ );
614
+ }
615
+
616
+ function DebugCustomerTrackedContent() {
617
+ const trackRef = useTrack({
618
+ experimentId: "warn-exp",
619
+ debug: true,
620
+ });
621
+ return (
622
+ <div ref={trackRef as React.RefObject<HTMLDivElement>} data-testid="tracked">
623
+ Content
624
+ </div>
625
+ );
626
+ }
627
+
628
+ render(
629
+ <NoCustomerProvider>
630
+ <DebugCustomerTrackedContent />
631
+ </NoCustomerProvider>
632
+ );
633
+
634
+ await act(async () => {
635
+ await vi.runAllTimersAsync();
636
+ });
637
+
638
+ expect(warnSpy).toHaveBeenCalledWith(
639
+ expect.stringContaining("no customerId")
640
+ );
641
+
642
+ warnSpy.mockRestore();
643
+ });
644
+ });
645
+
395
646
  // ── StrictMode safety ─────────────────────────────────────────────────────
396
647
 
397
648
  describe("useTrack StrictMode safety", () => {
@@ -3,9 +3,9 @@
3
3
  import React from "react";
4
4
  import { useTrack, type UseTrackOptions } from "../hooks/useTrack";
5
5
 
6
- export interface TrackProps extends UseTrackOptions {
6
+ export type TrackProps = UseTrackOptions & {
7
7
  children: React.ReactNode;
8
- }
8
+ };
9
9
 
10
10
  /**
11
11
  * Wrapper component that attaches impression and click tracking to its children.
@@ -25,7 +25,7 @@ export function Track({ children, ...trackOptions }: TrackProps) {
25
25
  <div
26
26
  ref={trackRef as React.RefObject<HTMLDivElement>}
27
27
  data-probat-track={trackOptions.experimentId}
28
- data-probat-variant={trackOptions.variantKey}
28
+ data-probat-variant={trackOptions.variantKey ?? "server-resolved"}
29
29
  style={{ display: "contents" }}
30
30
  >
31
31
  {children}
@@ -9,11 +9,9 @@ import { useStableInstanceId } from "../utils/stableInstanceId";
9
9
 
10
10
  // ── Types ──────────────────────────────────────────────────────────────────
11
11
 
12
- export interface UseTrackOptions {
12
+ interface UseTrackBaseOptions {
13
13
  /** Experiment identifier */
14
14
  experimentId: string;
15
- /** The variant key to attach to events */
16
- variantKey: string;
17
15
  /** Stable instance id when multiple instances of the same experiment exist on a page */
18
16
  componentInstanceId?: string;
19
17
  /** Auto-track impressions (default true) */
@@ -28,22 +26,44 @@ export interface UseTrackOptions {
28
26
  debug?: boolean;
29
27
  }
30
28
 
29
+ /** Explicit mode: pass the variant key directly */
30
+ export interface UseTrackExplicitOptions extends UseTrackBaseOptions {
31
+ /** The variant key to attach to events */
32
+ variantKey: string;
33
+ customerId?: undefined;
34
+ }
35
+
36
+ /** Customer mode: backend resolves variant from assignment table */
37
+ export interface UseTrackCustomerOptions extends UseTrackBaseOptions {
38
+ variantKey?: undefined;
39
+ /** Customer ID to resolve variant server-side. Falls back to provider's customerId. */
40
+ customerId?: string;
41
+ }
42
+
43
+ export type UseTrackOptions = UseTrackExplicitOptions | UseTrackCustomerOptions;
44
+
31
45
  // ── Hook ───────────────────────────────────────────────────────────────────
32
46
 
33
47
  /**
34
48
  * Attaches impression and click tracking to a DOM element via a ref.
35
- * Completely independent of variant assignment — pass the variantKey explicitly.
49
+ *
50
+ * Two modes:
51
+ * - **Explicit**: pass `variantKey` directly — stamped on every event.
52
+ * - **Customer**: omit `variantKey` — backend resolves it from the assignment table
53
+ * using `(experimentId, customerId)`.
36
54
  *
37
55
  * @example
38
56
  * ```tsx
57
+ * // Explicit mode
39
58
  * const trackRef = useTrack({ experimentId: "pricing", variantKey: "ai_v1" });
40
- * return <div ref={trackRef}>...</div>;
59
+ *
60
+ * // Customer mode (backend resolves variant)
61
+ * const trackRef = useTrack({ experimentId: "pricing", customerId: "user_123" });
41
62
  * ```
42
63
  */
43
64
  export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement | null> {
44
65
  const {
45
66
  experimentId,
46
- variantKey,
47
67
  componentInstanceId,
48
68
  impression: trackImpression = true,
49
69
  click: trackClick = true,
@@ -52,7 +72,14 @@ export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement
52
72
  debug = false,
53
73
  } = options;
54
74
 
55
- const { host, customerId } = useProbatContext();
75
+ const variantKey = options.variantKey ?? undefined;
76
+ const explicitCustomerId = "customerId" in options ? options.customerId : undefined;
77
+
78
+ const { host, customerId: providerCustomerId } = useProbatContext();
79
+
80
+ // In customer mode, use explicit customerId or fall back to provider's
81
+ const resolvedCustomerId = explicitCustomerId ?? providerCustomerId;
82
+ const isCustomerMode = !variantKey;
56
83
 
57
84
  const autoInstanceId = useStableInstanceId(experimentId);
58
85
  const instanceId = componentInstanceId ?? autoInstanceId;
@@ -60,25 +87,38 @@ export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement
60
87
  const containerRef = useRef<HTMLElement | null>(null);
61
88
  const impressionSent = useRef(false);
62
89
 
90
+ // Runtime warning
91
+ useEffect(() => {
92
+ if (isCustomerMode && !resolvedCustomerId && debug) {
93
+ console.warn(
94
+ `[probat] useTrack called without variantKey and no customerId ` +
95
+ `available for "${experimentId}". Events will have no variant attribution.`
96
+ );
97
+ }
98
+ }, [isCustomerMode, resolvedCustomerId, experimentId, debug]);
99
+
63
100
  const eventProps = useMemo(
64
101
  () => ({
65
102
  experiment_id: experimentId,
66
- variant_key: variantKey,
103
+ ...(variantKey ? { variant_key: variantKey } : {}),
67
104
  component_instance_id: instanceId,
68
- ...(customerId ? { distinct_id: customerId } : {}),
105
+ ...(resolvedCustomerId ? { distinct_id: resolvedCustomerId } : {}),
69
106
  }),
70
- [experimentId, variantKey, instanceId, customerId]
107
+ [experimentId, variantKey, instanceId, resolvedCustomerId]
71
108
  );
72
109
 
73
110
  // ── Impression tracking via IntersectionObserver ────────────────────────
74
111
 
112
+ // In customer mode, use customerId for dedupe instead of variantKey
113
+ const dedupeVariant = variantKey ?? resolvedCustomerId ?? "__anon__";
114
+
75
115
  useEffect(() => {
76
116
  if (!trackImpression) return;
77
117
 
78
118
  impressionSent.current = false;
79
119
 
80
120
  const pageKey = getPageKey();
81
- const dedupeKey = makeDedupeKey(experimentId, variantKey, instanceId, pageKey);
121
+ const dedupeKey = makeDedupeKey(experimentId, dedupeVariant, instanceId, pageKey);
82
122
 
83
123
  if (hasSeen(dedupeKey)) {
84
124
  impressionSent.current = true;
@@ -131,7 +171,7 @@ export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement
131
171
  }, [
132
172
  trackImpression,
133
173
  experimentId,
134
- variantKey,
174
+ dedupeVariant,
135
175
  instanceId,
136
176
  host,
137
177
  impressionEventName,
package/src/index.ts CHANGED
@@ -16,7 +16,7 @@ export type { TrackProps } from "./components/Track";
16
16
  export { useExperiment } from "./hooks/useExperiment";
17
17
  export type { UseExperimentOptions, UseExperimentReturn } from "./hooks/useExperiment";
18
18
  export { useTrack } from "./hooks/useTrack";
19
- export type { UseTrackOptions } from "./hooks/useTrack";
19
+ export type { UseTrackOptions, UseTrackExplicitOptions, UseTrackCustomerOptions } from "./hooks/useTrack";
20
20
  export { useProbatMetrics } from "./hooks/useProbatMetrics";
21
21
  export type { UseProbatMetricsReturn } from "./hooks/useProbatMetrics";
22
22