@probat/react 0.4.3 → 0.4.5

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
@@ -6,6 +6,8 @@ interface ProbatProviderProps {
6
6
  customerId?: string;
7
7
  /** Base URL for the Probat API. Defaults to https://gushi.onrender.com */
8
8
  host?: string;
9
+ /** API key for authenticating SDK requests (probat_sk_...) */
10
+ apiKey?: string;
9
11
  /**
10
12
  * Bootstrap assignments to avoid flash on first render.
11
13
  * Map of experiment id → variant key.
@@ -69,6 +71,8 @@ interface UseTrackBaseOptions {
69
71
  experimentId: string;
70
72
  /** Stable instance id when multiple instances of the same experiment exist on a page */
71
73
  componentInstanceId?: string;
74
+ /** Whether the variant assignment has been resolved. When false, all tracking is suppressed. */
75
+ resolved?: boolean;
72
76
  /** Auto-track impressions (default true) */
73
77
  impression?: boolean;
74
78
  /** Auto-track clicks (default true) */
@@ -180,11 +184,11 @@ interface MetricPayload {
180
184
  * Returns the variant key string (e.g. "control", "ai_v1").
181
185
  * Deduplicates concurrent calls for the same experiment.
182
186
  */
183
- declare function fetchDecision(host: string, experimentId: string, distinctId: string): Promise<string>;
187
+ declare function fetchDecision(host: string, experimentId: string, distinctId: string, apiKey?: string): Promise<string>;
184
188
  /**
185
189
  * Fire-and-forget metric send. Never throws.
186
190
  */
187
- declare function sendMetric(host: string, event: string, properties: Record<string, unknown>): void;
191
+ declare function sendMetric(host: string, event: string, properties: Record<string, unknown>, apiKey?: string): void;
188
192
 
189
193
  /**
190
194
  * Factory that creates a typed context + hook pair for passing a variantKey
package/dist/index.d.ts CHANGED
@@ -6,6 +6,8 @@ interface ProbatProviderProps {
6
6
  customerId?: string;
7
7
  /** Base URL for the Probat API. Defaults to https://gushi.onrender.com */
8
8
  host?: string;
9
+ /** API key for authenticating SDK requests (probat_sk_...) */
10
+ apiKey?: string;
9
11
  /**
10
12
  * Bootstrap assignments to avoid flash on first render.
11
13
  * Map of experiment id → variant key.
@@ -69,6 +71,8 @@ interface UseTrackBaseOptions {
69
71
  experimentId: string;
70
72
  /** Stable instance id when multiple instances of the same experiment exist on a page */
71
73
  componentInstanceId?: string;
74
+ /** Whether the variant assignment has been resolved. When false, all tracking is suppressed. */
75
+ resolved?: boolean;
72
76
  /** Auto-track impressions (default true) */
73
77
  impression?: boolean;
74
78
  /** Auto-track clicks (default true) */
@@ -180,11 +184,11 @@ interface MetricPayload {
180
184
  * Returns the variant key string (e.g. "control", "ai_v1").
181
185
  * Deduplicates concurrent calls for the same experiment.
182
186
  */
183
- declare function fetchDecision(host: string, experimentId: string, distinctId: string): Promise<string>;
187
+ declare function fetchDecision(host: string, experimentId: string, distinctId: string, apiKey?: string): Promise<string>;
184
188
  /**
185
189
  * Fire-and-forget metric send. Never throws.
186
190
  */
187
- declare function sendMetric(host: string, event: string, properties: Record<string, unknown>): void;
191
+ declare function sendMetric(host: string, event: string, properties: Record<string, unknown>, apiKey?: string): void;
188
192
 
189
193
  /**
190
194
  * Factory that creates a typed context + hook pair for passing a variantKey
package/dist/index.js CHANGED
@@ -9,20 +9,22 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
9
  var React3__default = /*#__PURE__*/_interopDefault(React3);
10
10
 
11
11
  var ProbatContext = React3.createContext(null);
12
- var DEFAULT_HOST = "https://gushi.onrender.com";
12
+ var DEFAULT_HOST = "https://api.probat.app";
13
13
  function ProbatProvider({
14
14
  customerId,
15
15
  host = DEFAULT_HOST,
16
+ apiKey,
16
17
  bootstrap,
17
18
  children
18
19
  }) {
19
20
  const value = React3.useMemo(
20
21
  () => ({
21
22
  host: host.replace(/\/$/, ""),
23
+ apiKey,
22
24
  customerId,
23
25
  bootstrap: bootstrap ?? {}
24
26
  }),
25
- [customerId, host, bootstrap]
27
+ [customerId, host, apiKey, bootstrap]
26
28
  );
27
29
  return /* @__PURE__ */ React3__default.default.createElement(ProbatContext.Provider, { value }, children);
28
30
  }
@@ -132,18 +134,20 @@ function buildEventContext() {
132
134
 
133
135
  // src/utils/api.ts
134
136
  var pendingDecisions = /* @__PURE__ */ new Map();
135
- async function fetchDecision(host, experimentId, distinctId) {
137
+ async function fetchDecision(host, experimentId, distinctId, apiKey) {
136
138
  const existing = pendingDecisions.get(experimentId);
137
139
  if (existing) return existing;
138
140
  const promise = (async () => {
139
141
  try {
140
142
  const url = `${host.replace(/\/$/, "")}/experiment/decide`;
143
+ const headers = {
144
+ "Content-Type": "application/json",
145
+ Accept: "application/json"
146
+ };
147
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
141
148
  const res = await fetch(url, {
142
149
  method: "POST",
143
- headers: {
144
- "Content-Type": "application/json",
145
- Accept: "application/json"
146
- },
150
+ headers,
147
151
  credentials: "include",
148
152
  body: JSON.stringify({
149
153
  experiment_id: experimentId,
@@ -160,7 +164,7 @@ async function fetchDecision(host, experimentId, distinctId) {
160
164
  pendingDecisions.set(experimentId, promise);
161
165
  return promise;
162
166
  }
163
- function sendMetric(host, event, properties) {
167
+ function sendMetric(host, event, properties, apiKey) {
164
168
  if (typeof window === "undefined") return;
165
169
  const ctx = buildEventContext();
166
170
  const payload = {
@@ -175,9 +179,11 @@ function sendMetric(host, event, properties) {
175
179
  };
176
180
  try {
177
181
  const url = `${host.replace(/\/$/, "")}/experiment/metrics`;
182
+ const headers = { "Content-Type": "application/json" };
183
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
178
184
  fetch(url, {
179
185
  method: "POST",
180
- headers: { "Content-Type": "application/json" },
186
+ headers,
181
187
  credentials: "include",
182
188
  body: JSON.stringify(payload)
183
189
  }).catch(() => {
@@ -227,7 +233,7 @@ function writeAssignment(id, variantKey) {
227
233
  }
228
234
  function useExperiment(id, options = {}) {
229
235
  const { fallback = "control", debug = false } = options;
230
- const { host, bootstrap, customerId } = useProbatContext();
236
+ const { host, bootstrap, customerId, apiKey } = useProbatContext();
231
237
  const [variantKey, setVariantKey] = React3.useState(() => {
232
238
  if (bootstrap[id]) return bootstrap[id];
233
239
  return "control";
@@ -246,7 +252,7 @@ function useExperiment(id, options = {}) {
246
252
  (async () => {
247
253
  try {
248
254
  const distinctId = customerId ?? getDistinctId();
249
- const key = await fetchDecision(host, id, distinctId);
255
+ const key = await fetchDecision(host, id, distinctId, apiKey);
250
256
  if (cancelled) return;
251
257
  setVariantKey(key);
252
258
  writeAssignment(id, key);
@@ -365,6 +371,7 @@ function useTrack(options) {
365
371
  const {
366
372
  experimentId,
367
373
  componentInstanceId,
374
+ resolved = true,
368
375
  impression: trackImpression = true,
369
376
  click: trackClick = true,
370
377
  impressionEventName = "$experiment_exposure",
@@ -373,7 +380,7 @@ function useTrack(options) {
373
380
  } = options;
374
381
  const variantKey = options.variantKey ?? void 0;
375
382
  const explicitCustomerId = "customerId" in options ? options.customerId : void 0;
376
- const { host, customerId: providerCustomerId } = useProbatContext();
383
+ const { host, customerId: providerCustomerId, apiKey } = useProbatContext();
377
384
  const resolvedCustomerId = explicitCustomerId ?? providerCustomerId;
378
385
  const isCustomerMode = !variantKey;
379
386
  const autoInstanceId = useStableInstanceId(experimentId);
@@ -398,7 +405,7 @@ function useTrack(options) {
398
405
  );
399
406
  const dedupeVariant = variantKey ?? resolvedCustomerId ?? "__anon__";
400
407
  React3.useEffect(() => {
401
- if (!trackImpression) return;
408
+ if (!trackImpression || !resolved) return;
402
409
  impressionSent.current = false;
403
410
  const pageKey = getPageKey();
404
411
  const dedupeKey = makeDedupeKey(experimentId, dedupeVariant, instanceId, pageKey);
@@ -412,7 +419,7 @@ function useTrack(options) {
412
419
  if (!impressionSent.current) {
413
420
  impressionSent.current = true;
414
421
  markSeen(dedupeKey);
415
- sendMetric(host, impressionEventName, eventProps);
422
+ sendMetric(host, impressionEventName, eventProps, apiKey);
416
423
  if (debug) console.log(`[probat] Impression sent (no IO) for "${experimentId}"`);
417
424
  }
418
425
  return;
@@ -426,7 +433,7 @@ function useTrack(options) {
426
433
  if (impressionSent.current) return;
427
434
  impressionSent.current = true;
428
435
  markSeen(dedupeKey);
429
- sendMetric(host, impressionEventName, eventProps);
436
+ sendMetric(host, impressionEventName, eventProps, apiKey);
430
437
  if (debug) console.log(`[probat] Impression sent for "${experimentId}"`);
431
438
  observer.disconnect();
432
439
  }, 250);
@@ -444,6 +451,7 @@ function useTrack(options) {
444
451
  };
445
452
  }, [
446
453
  trackImpression,
454
+ resolved,
447
455
  experimentId,
448
456
  dedupeVariant,
449
457
  instanceId,
@@ -454,18 +462,18 @@ function useTrack(options) {
454
462
  ]);
455
463
  const handleClick = React3.useCallback(
456
464
  (e) => {
457
- if (!trackClick) return;
465
+ if (!trackClick || !resolved) return;
458
466
  const meta = extractClickMeta(e.target);
459
467
  if (!meta) return;
460
468
  sendMetric(host, clickEventName, {
461
469
  ...eventProps,
462
470
  ...meta
463
- });
471
+ }, apiKey);
464
472
  if (debug) {
465
473
  console.log(`[probat] Click tracked for "${experimentId}"`, meta);
466
474
  }
467
475
  },
468
- [trackClick, host, clickEventName, eventProps, experimentId, debug]
476
+ [trackClick, resolved, host, clickEventName, eventProps, experimentId, debug]
469
477
  );
470
478
  React3.useEffect(() => {
471
479
  const el = containerRef.current;
@@ -499,7 +507,8 @@ function Experiment({
499
507
  experimentId: id,
500
508
  variantKey,
501
509
  componentInstanceId,
502
- impression: resolved ? track?.impression !== false : false,
510
+ resolved,
511
+ impression: track?.impression !== false,
503
512
  click: track?.primaryClick !== false,
504
513
  impressionEventName: track?.impressionEventName,
505
514
  clickEventName: track?.clickEventName,
@@ -537,15 +546,15 @@ function Track({ children, ...trackOptions }) {
537
546
  );
538
547
  }
539
548
  function useProbatMetrics() {
540
- const { host, customerId } = useProbatContext();
549
+ const { host, customerId, apiKey } = useProbatContext();
541
550
  const capture = React3.useCallback(
542
551
  (event, properties = {}) => {
543
552
  sendMetric(host, event, {
544
553
  ...customerId ? { distinct_id: customerId } : {},
545
554
  ...properties
546
- });
555
+ }, apiKey);
547
556
  },
548
- [host, customerId]
557
+ [host, customerId, apiKey]
549
558
  );
550
559
  return { capture };
551
560
  }
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\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"]}
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 apiKey?: string;\n customerId?: string;\n bootstrap: Record<string, string>;\n}\n\nconst ProbatContext = createContext<ProbatContextValue | null>(null);\n\nconst DEFAULT_HOST = \"https://api.probat.app\";\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 /** API key for authenticating SDK requests (probat_sk_...) */\n apiKey?: 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 apiKey,\n bootstrap,\n children,\n}: ProbatProviderProps) {\n const value = useMemo<ProbatContextValue>(\n () => ({\n host: host.replace(/\\/$/, \"\"),\n apiKey,\n customerId,\n bootstrap: bootstrap ?? {},\n }),\n [customerId, host, apiKey, 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 apiKey?: 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 headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n };\n if (apiKey) headers[\"Authorization\"] = `Bearer ${apiKey}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\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 apiKey?: string\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 const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (apiKey) headers[\"Authorization\"] = `Bearer ${apiKey}`;\n fetch(url, {\n method: \"POST\",\n headers,\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, apiKey } = 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, apiKey);\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 /** Whether the variant assignment has been resolved. When false, all tracking is suppressed. */\n resolved?: boolean;\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 resolved = true,\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, apiKey } = 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 || !resolved) 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, apiKey);\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, apiKey);\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 resolved,\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 || !resolved) return;\n\n const meta = extractClickMeta(e.target as EventTarget);\n if (!meta) return;\n\n sendMetric(host, clickEventName, {\n ...eventProps,\n ...meta,\n }, apiKey);\n if (debug) {\n console.log(`[probat] Click tracked for \"${experimentId}\"`, meta);\n }\n },\n [trackClick, resolved, 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 resolved,\n impression: track?.impression !== 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, apiKey } = 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 }, apiKey);\n },\n [host, customerId, apiKey]\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
@@ -3,20 +3,22 @@
3
3
  import React3, { createContext, useRef, useState, useEffect, useMemo, useCallback, useContext } from 'react';
4
4
 
5
5
  var ProbatContext = createContext(null);
6
- var DEFAULT_HOST = "https://gushi.onrender.com";
6
+ var DEFAULT_HOST = "https://api.probat.app";
7
7
  function ProbatProvider({
8
8
  customerId,
9
9
  host = DEFAULT_HOST,
10
+ apiKey,
10
11
  bootstrap,
11
12
  children
12
13
  }) {
13
14
  const value = useMemo(
14
15
  () => ({
15
16
  host: host.replace(/\/$/, ""),
17
+ apiKey,
16
18
  customerId,
17
19
  bootstrap: bootstrap ?? {}
18
20
  }),
19
- [customerId, host, bootstrap]
21
+ [customerId, host, apiKey, bootstrap]
20
22
  );
21
23
  return /* @__PURE__ */ React3.createElement(ProbatContext.Provider, { value }, children);
22
24
  }
@@ -126,18 +128,20 @@ function buildEventContext() {
126
128
 
127
129
  // src/utils/api.ts
128
130
  var pendingDecisions = /* @__PURE__ */ new Map();
129
- async function fetchDecision(host, experimentId, distinctId) {
131
+ async function fetchDecision(host, experimentId, distinctId, apiKey) {
130
132
  const existing = pendingDecisions.get(experimentId);
131
133
  if (existing) return existing;
132
134
  const promise = (async () => {
133
135
  try {
134
136
  const url = `${host.replace(/\/$/, "")}/experiment/decide`;
137
+ const headers = {
138
+ "Content-Type": "application/json",
139
+ Accept: "application/json"
140
+ };
141
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
135
142
  const res = await fetch(url, {
136
143
  method: "POST",
137
- headers: {
138
- "Content-Type": "application/json",
139
- Accept: "application/json"
140
- },
144
+ headers,
141
145
  credentials: "include",
142
146
  body: JSON.stringify({
143
147
  experiment_id: experimentId,
@@ -154,7 +158,7 @@ async function fetchDecision(host, experimentId, distinctId) {
154
158
  pendingDecisions.set(experimentId, promise);
155
159
  return promise;
156
160
  }
157
- function sendMetric(host, event, properties) {
161
+ function sendMetric(host, event, properties, apiKey) {
158
162
  if (typeof window === "undefined") return;
159
163
  const ctx = buildEventContext();
160
164
  const payload = {
@@ -169,9 +173,11 @@ function sendMetric(host, event, properties) {
169
173
  };
170
174
  try {
171
175
  const url = `${host.replace(/\/$/, "")}/experiment/metrics`;
176
+ const headers = { "Content-Type": "application/json" };
177
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
172
178
  fetch(url, {
173
179
  method: "POST",
174
- headers: { "Content-Type": "application/json" },
180
+ headers,
175
181
  credentials: "include",
176
182
  body: JSON.stringify(payload)
177
183
  }).catch(() => {
@@ -221,7 +227,7 @@ function writeAssignment(id, variantKey) {
221
227
  }
222
228
  function useExperiment(id, options = {}) {
223
229
  const { fallback = "control", debug = false } = options;
224
- const { host, bootstrap, customerId } = useProbatContext();
230
+ const { host, bootstrap, customerId, apiKey } = useProbatContext();
225
231
  const [variantKey, setVariantKey] = useState(() => {
226
232
  if (bootstrap[id]) return bootstrap[id];
227
233
  return "control";
@@ -240,7 +246,7 @@ function useExperiment(id, options = {}) {
240
246
  (async () => {
241
247
  try {
242
248
  const distinctId = customerId ?? getDistinctId();
243
- const key = await fetchDecision(host, id, distinctId);
249
+ const key = await fetchDecision(host, id, distinctId, apiKey);
244
250
  if (cancelled) return;
245
251
  setVariantKey(key);
246
252
  writeAssignment(id, key);
@@ -359,6 +365,7 @@ function useTrack(options) {
359
365
  const {
360
366
  experimentId,
361
367
  componentInstanceId,
368
+ resolved = true,
362
369
  impression: trackImpression = true,
363
370
  click: trackClick = true,
364
371
  impressionEventName = "$experiment_exposure",
@@ -367,7 +374,7 @@ function useTrack(options) {
367
374
  } = options;
368
375
  const variantKey = options.variantKey ?? void 0;
369
376
  const explicitCustomerId = "customerId" in options ? options.customerId : void 0;
370
- const { host, customerId: providerCustomerId } = useProbatContext();
377
+ const { host, customerId: providerCustomerId, apiKey } = useProbatContext();
371
378
  const resolvedCustomerId = explicitCustomerId ?? providerCustomerId;
372
379
  const isCustomerMode = !variantKey;
373
380
  const autoInstanceId = useStableInstanceId(experimentId);
@@ -392,7 +399,7 @@ function useTrack(options) {
392
399
  );
393
400
  const dedupeVariant = variantKey ?? resolvedCustomerId ?? "__anon__";
394
401
  useEffect(() => {
395
- if (!trackImpression) return;
402
+ if (!trackImpression || !resolved) return;
396
403
  impressionSent.current = false;
397
404
  const pageKey = getPageKey();
398
405
  const dedupeKey = makeDedupeKey(experimentId, dedupeVariant, instanceId, pageKey);
@@ -406,7 +413,7 @@ function useTrack(options) {
406
413
  if (!impressionSent.current) {
407
414
  impressionSent.current = true;
408
415
  markSeen(dedupeKey);
409
- sendMetric(host, impressionEventName, eventProps);
416
+ sendMetric(host, impressionEventName, eventProps, apiKey);
410
417
  if (debug) console.log(`[probat] Impression sent (no IO) for "${experimentId}"`);
411
418
  }
412
419
  return;
@@ -420,7 +427,7 @@ function useTrack(options) {
420
427
  if (impressionSent.current) return;
421
428
  impressionSent.current = true;
422
429
  markSeen(dedupeKey);
423
- sendMetric(host, impressionEventName, eventProps);
430
+ sendMetric(host, impressionEventName, eventProps, apiKey);
424
431
  if (debug) console.log(`[probat] Impression sent for "${experimentId}"`);
425
432
  observer.disconnect();
426
433
  }, 250);
@@ -438,6 +445,7 @@ function useTrack(options) {
438
445
  };
439
446
  }, [
440
447
  trackImpression,
448
+ resolved,
441
449
  experimentId,
442
450
  dedupeVariant,
443
451
  instanceId,
@@ -448,18 +456,18 @@ function useTrack(options) {
448
456
  ]);
449
457
  const handleClick = useCallback(
450
458
  (e) => {
451
- if (!trackClick) return;
459
+ if (!trackClick || !resolved) return;
452
460
  const meta = extractClickMeta(e.target);
453
461
  if (!meta) return;
454
462
  sendMetric(host, clickEventName, {
455
463
  ...eventProps,
456
464
  ...meta
457
- });
465
+ }, apiKey);
458
466
  if (debug) {
459
467
  console.log(`[probat] Click tracked for "${experimentId}"`, meta);
460
468
  }
461
469
  },
462
- [trackClick, host, clickEventName, eventProps, experimentId, debug]
470
+ [trackClick, resolved, host, clickEventName, eventProps, experimentId, debug]
463
471
  );
464
472
  useEffect(() => {
465
473
  const el = containerRef.current;
@@ -493,7 +501,8 @@ function Experiment({
493
501
  experimentId: id,
494
502
  variantKey,
495
503
  componentInstanceId,
496
- impression: resolved ? track?.impression !== false : false,
504
+ resolved,
505
+ impression: track?.impression !== false,
497
506
  click: track?.primaryClick !== false,
498
507
  impressionEventName: track?.impressionEventName,
499
508
  clickEventName: track?.clickEventName,
@@ -531,15 +540,15 @@ function Track({ children, ...trackOptions }) {
531
540
  );
532
541
  }
533
542
  function useProbatMetrics() {
534
- const { host, customerId } = useProbatContext();
543
+ const { host, customerId, apiKey } = useProbatContext();
535
544
  const capture = useCallback(
536
545
  (event, properties = {}) => {
537
546
  sendMetric(host, event, {
538
547
  ...customerId ? { distinct_id: customerId } : {},
539
548
  ...properties
540
- });
549
+ }, apiKey);
541
550
  },
542
- [host, customerId]
551
+ [host, customerId, apiKey]
543
552
  );
544
553
  return { capture };
545
554
  }
@@ -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","useEffect","useMemo","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\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"]}
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":"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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 apiKey?: string;\n customerId?: string;\n bootstrap: Record<string, string>;\n}\n\nconst ProbatContext = createContext<ProbatContextValue | null>(null);\n\nconst DEFAULT_HOST = \"https://api.probat.app\";\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 /** API key for authenticating SDK requests (probat_sk_...) */\n apiKey?: 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 apiKey,\n bootstrap,\n children,\n}: ProbatProviderProps) {\n const value = useMemo<ProbatContextValue>(\n () => ({\n host: host.replace(/\\/$/, \"\"),\n apiKey,\n customerId,\n bootstrap: bootstrap ?? {},\n }),\n [customerId, host, apiKey, 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 apiKey?: 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 headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n };\n if (apiKey) headers[\"Authorization\"] = `Bearer ${apiKey}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\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 apiKey?: string\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 const headers: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (apiKey) headers[\"Authorization\"] = `Bearer ${apiKey}`;\n fetch(url, {\n method: \"POST\",\n headers,\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, apiKey } = 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, apiKey);\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 /** Whether the variant assignment has been resolved. When false, all tracking is suppressed. */\n resolved?: boolean;\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 resolved = true,\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, apiKey } = 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 || !resolved) 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, apiKey);\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, apiKey);\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 resolved,\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 || !resolved) return;\n\n const meta = extractClickMeta(e.target as EventTarget);\n if (!meta) return;\n\n sendMetric(host, clickEventName, {\n ...eventProps,\n ...meta,\n }, apiKey);\n if (debug) {\n console.log(`[probat] Click tracked for \"${experimentId}\"`, meta);\n }\n },\n [trackClick, resolved, 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 resolved,\n impression: track?.impression !== 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, apiKey } = 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 }, apiKey);\n },\n [host, customerId, apiKey]\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.3",
3
+ "version": "0.4.5",
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
  },
@@ -63,7 +63,8 @@ export function Experiment({
63
63
  experimentId: id,
64
64
  variantKey,
65
65
  componentInstanceId,
66
- impression: resolved ? (track?.impression !== false) : false,
66
+ resolved,
67
+ impression: track?.impression !== false,
67
68
  click: track?.primaryClick !== false,
68
69
  impressionEventName: track?.impressionEventName,
69
70
  clickEventName: track?.clickEventName,
@@ -4,13 +4,14 @@ import React, { createContext, useContext, useMemo } from "react";
4
4
 
5
5
  export interface ProbatContextValue {
6
6
  host: string;
7
+ apiKey?: string;
7
8
  customerId?: string;
8
9
  bootstrap: Record<string, string>;
9
10
  }
10
11
 
11
12
  const ProbatContext = createContext<ProbatContextValue | null>(null);
12
13
 
13
- const DEFAULT_HOST = "https://gushi.onrender.com";
14
+ const DEFAULT_HOST = "https://api.probat.app";
14
15
 
15
16
  export interface ProbatProviderProps {
16
17
  /** Your end-user's ID. When provided, used as the distinct_id for variant
@@ -18,6 +19,8 @@ export interface ProbatProviderProps {
18
19
  customerId?: string;
19
20
  /** Base URL for the Probat API. Defaults to https://gushi.onrender.com */
20
21
  host?: string;
22
+ /** API key for authenticating SDK requests (probat_sk_...) */
23
+ apiKey?: string;
21
24
  /**
22
25
  * Bootstrap assignments to avoid flash on first render.
23
26
  * Map of experiment id → variant key.
@@ -30,16 +33,18 @@ export interface ProbatProviderProps {
30
33
  export function ProbatProvider({
31
34
  customerId,
32
35
  host = DEFAULT_HOST,
36
+ apiKey,
33
37
  bootstrap,
34
38
  children,
35
39
  }: ProbatProviderProps) {
36
40
  const value = useMemo<ProbatContextValue>(
37
41
  () => ({
38
42
  host: host.replace(/\/$/, ""),
43
+ apiKey,
39
44
  customerId,
40
45
  bootstrap: bootstrap ?? {},
41
46
  }),
42
- [customerId, host, bootstrap]
47
+ [customerId, host, apiKey, bootstrap]
43
48
  );
44
49
 
45
50
  return (
@@ -63,7 +63,7 @@ export function useExperiment(
63
63
  options: UseExperimentOptions = {}
64
64
  ): UseExperimentReturn {
65
65
  const { fallback = "control", debug = false } = options;
66
- const { host, bootstrap, customerId } = useProbatContext();
66
+ const { host, bootstrap, customerId, apiKey } = useProbatContext();
67
67
 
68
68
  const [variantKey, setVariantKey] = useState<string>(() => {
69
69
  if (bootstrap[id]) return bootstrap[id];
@@ -86,7 +86,7 @@ export function useExperiment(
86
86
  (async () => {
87
87
  try {
88
88
  const distinctId = customerId ?? getDistinctId();
89
- const key = await fetchDecision(host, id, distinctId);
89
+ const key = await fetchDecision(host, id, distinctId, apiKey);
90
90
  if (cancelled) return;
91
91
 
92
92
  setVariantKey(key);
@@ -23,16 +23,16 @@ export interface UseProbatMetricsReturn {
23
23
  * that sends events to the Probat backend using the provider's host config.
24
24
  */
25
25
  export function useProbatMetrics(): UseProbatMetricsReturn {
26
- const { host, customerId } = useProbatContext();
26
+ const { host, customerId, apiKey } = useProbatContext();
27
27
 
28
28
  const capture = useCallback(
29
29
  (event: string, properties: Record<string, unknown> = {}) => {
30
30
  sendMetric(host, event, {
31
31
  ...(customerId ? { distinct_id: customerId } : {}),
32
32
  ...properties,
33
- });
33
+ }, apiKey);
34
34
  },
35
- [host, customerId]
35
+ [host, customerId, apiKey]
36
36
  );
37
37
 
38
38
  return { capture };
@@ -14,6 +14,8 @@ interface UseTrackBaseOptions {
14
14
  experimentId: string;
15
15
  /** Stable instance id when multiple instances of the same experiment exist on a page */
16
16
  componentInstanceId?: string;
17
+ /** Whether the variant assignment has been resolved. When false, all tracking is suppressed. */
18
+ resolved?: boolean;
17
19
  /** Auto-track impressions (default true) */
18
20
  impression?: boolean;
19
21
  /** Auto-track clicks (default true) */
@@ -65,6 +67,7 @@ export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement
65
67
  const {
66
68
  experimentId,
67
69
  componentInstanceId,
70
+ resolved = true,
68
71
  impression: trackImpression = true,
69
72
  click: trackClick = true,
70
73
  impressionEventName = "$experiment_exposure",
@@ -75,7 +78,7 @@ export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement
75
78
  const variantKey = options.variantKey ?? undefined;
76
79
  const explicitCustomerId = "customerId" in options ? options.customerId : undefined;
77
80
 
78
- const { host, customerId: providerCustomerId } = useProbatContext();
81
+ const { host, customerId: providerCustomerId, apiKey } = useProbatContext();
79
82
 
80
83
  // In customer mode, use explicit customerId or fall back to provider's
81
84
  const resolvedCustomerId = explicitCustomerId ?? providerCustomerId;
@@ -113,7 +116,7 @@ export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement
113
116
  const dedupeVariant = variantKey ?? resolvedCustomerId ?? "__anon__";
114
117
 
115
118
  useEffect(() => {
116
- if (!trackImpression) return;
119
+ if (!trackImpression || !resolved) return;
117
120
 
118
121
  impressionSent.current = false;
119
122
 
@@ -133,7 +136,7 @@ export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement
133
136
  if (!impressionSent.current) {
134
137
  impressionSent.current = true;
135
138
  markSeen(dedupeKey);
136
- sendMetric(host, impressionEventName, eventProps);
139
+ sendMetric(host, impressionEventName, eventProps, apiKey);
137
140
  if (debug) console.log(`[probat] Impression sent (no IO) for "${experimentId}"`);
138
141
  }
139
142
  return;
@@ -150,7 +153,7 @@ export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement
150
153
  if (impressionSent.current) return;
151
154
  impressionSent.current = true;
152
155
  markSeen(dedupeKey);
153
- sendMetric(host, impressionEventName, eventProps);
156
+ sendMetric(host, impressionEventName, eventProps, apiKey);
154
157
  if (debug) console.log(`[probat] Impression sent for "${experimentId}"`);
155
158
  observer.disconnect();
156
159
  }, 250);
@@ -170,6 +173,7 @@ export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement
170
173
  };
171
174
  }, [
172
175
  trackImpression,
176
+ resolved,
173
177
  experimentId,
174
178
  dedupeVariant,
175
179
  instanceId,
@@ -183,7 +187,7 @@ export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement
183
187
 
184
188
  const handleClick = useCallback(
185
189
  (e: Event) => {
186
- if (!trackClick) return;
190
+ if (!trackClick || !resolved) return;
187
191
 
188
192
  const meta = extractClickMeta(e.target as EventTarget);
189
193
  if (!meta) return;
@@ -191,12 +195,12 @@ export function useTrack(options: UseTrackOptions): React.RefObject<HTMLElement
191
195
  sendMetric(host, clickEventName, {
192
196
  ...eventProps,
193
197
  ...meta,
194
- });
198
+ }, apiKey);
195
199
  if (debug) {
196
200
  console.log(`[probat] Click tracked for "${experimentId}"`, meta);
197
201
  }
198
202
  },
199
- [trackClick, host, clickEventName, eventProps, experimentId, debug]
203
+ [trackClick, resolved, host, clickEventName, eventProps, experimentId, debug]
200
204
  );
201
205
 
202
206
  useEffect(() => {
package/src/utils/api.ts CHANGED
@@ -25,7 +25,8 @@ const pendingDecisions = new Map<string, Promise<string>>();
25
25
  export async function fetchDecision(
26
26
  host: string,
27
27
  experimentId: string,
28
- distinctId: string
28
+ distinctId: string,
29
+ apiKey?: string
29
30
  ): Promise<string> {
30
31
  const existing = pendingDecisions.get(experimentId);
31
32
  if (existing) return existing;
@@ -33,12 +34,14 @@ export async function fetchDecision(
33
34
  const promise = (async () => {
34
35
  try {
35
36
  const url = `${host.replace(/\/$/, "")}/experiment/decide`;
37
+ const headers: Record<string, string> = {
38
+ "Content-Type": "application/json",
39
+ Accept: "application/json",
40
+ };
41
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
36
42
  const res = await fetch(url, {
37
43
  method: "POST",
38
- headers: {
39
- "Content-Type": "application/json",
40
- Accept: "application/json",
41
- },
44
+ headers,
42
45
  credentials: "include",
43
46
  body: JSON.stringify({
44
47
  experiment_id: experimentId,
@@ -65,7 +68,8 @@ export async function fetchDecision(
65
68
  export function sendMetric(
66
69
  host: string,
67
70
  event: string,
68
- properties: Record<string, unknown>
71
+ properties: Record<string, unknown>,
72
+ apiKey?: string
69
73
  ): void {
70
74
  if (typeof window === "undefined") return;
71
75
 
@@ -83,9 +87,11 @@ export function sendMetric(
83
87
 
84
88
  try {
85
89
  const url = `${host.replace(/\/$/, "")}/experiment/metrics`;
90
+ const headers: Record<string, string> = { "Content-Type": "application/json" };
91
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
86
92
  fetch(url, {
87
93
  method: "POST",
88
- headers: { "Content-Type": "application/json" },
94
+ headers,
89
95
  credentials: "include",
90
96
  body: JSON.stringify(payload),
91
97
  }).catch(() => {});