@probat/react 0.4.4 → 0.4.6

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.mjs CHANGED
@@ -2,56 +2,34 @@
2
2
  "use client";
3
3
  import React3, { createContext, useRef, useState, useEffect, useMemo, useCallback, useContext } from 'react';
4
4
 
5
- var ProbatContext = createContext(null);
6
- var DEFAULT_HOST = "https://gushi.onrender.com";
7
- function ProbatProvider({
8
- customerId,
9
- host = DEFAULT_HOST,
10
- bootstrap,
11
- children
12
- }) {
13
- const value = useMemo(
14
- () => ({
15
- host: host.replace(/\/$/, ""),
16
- customerId,
17
- bootstrap: bootstrap ?? {}
18
- }),
19
- [customerId, host, bootstrap]
20
- );
21
- return /* @__PURE__ */ React3.createElement(ProbatContext.Provider, { value }, children);
22
- }
23
- function useProbatContext() {
24
- const ctx = useContext(ProbatContext);
25
- if (!ctx) {
26
- throw new Error(
27
- "useProbatContext must be used within <ProbatProviderClient>. Wrap your app with <ProbatProviderClient>."
28
- );
29
- }
30
- return ctx;
31
- }
32
-
33
- // src/components/ProbatProviderClient.tsx
34
- function ProbatProviderClient(props) {
35
- return React3.createElement(ProbatProvider, props);
36
- }
5
+ // src/types/events.ts
6
+ var PROBAT_ENV_DEV = "dev";
7
+ var PROBAT_ENV_PROD = "prod";
37
8
 
38
9
  // src/utils/environment.ts
39
10
  function detectEnvironment() {
40
11
  if (typeof window === "undefined") {
41
- return "prod";
12
+ return PROBAT_ENV_PROD;
42
13
  }
43
14
  const hostname = window.location.hostname;
44
15
  if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname.startsWith("192.168.") || hostname.startsWith("10.") || hostname.startsWith("172.16.") || hostname.startsWith("172.17.") || hostname.startsWith("172.18.") || hostname.startsWith("172.19.") || hostname.startsWith("172.20.") || hostname.startsWith("172.21.") || hostname.startsWith("172.22.") || hostname.startsWith("172.23.") || hostname.startsWith("172.24.") || hostname.startsWith("172.25.") || hostname.startsWith("172.26.") || hostname.startsWith("172.27.") || hostname.startsWith("172.28.") || hostname.startsWith("172.29.") || hostname.startsWith("172.30.") || hostname.startsWith("172.31.")) {
45
- return "dev";
16
+ return PROBAT_ENV_DEV;
46
17
  }
47
- return "prod";
18
+ return PROBAT_ENV_PROD;
48
19
  }
49
20
 
50
21
  // src/utils/eventContext.ts
51
22
  var DISTINCT_ID_KEY = "probat:distinct_id";
52
23
  var SESSION_ID_KEY = "probat:session_id";
24
+ var SESSION_START_AT_KEY = "probat:session_start_at";
25
+ var SESSION_SEQUENCE_KEY = "probat:session_sequence";
26
+ var SESSION_LAST_ACTIVITY_AT_KEY = "probat:session_last_activity_at";
27
+ var SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
53
28
  var cachedDistinctId = null;
54
29
  var cachedSessionId = null;
30
+ var cachedSessionStartAt = null;
31
+ var cachedSessionSequence = 0;
32
+ var cachedLastActivityAt = 0;
55
33
  function generateId() {
56
34
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
57
35
  return crypto.randomUUID();
@@ -64,6 +42,77 @@ function generateId() {
64
42
  }
65
43
  return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
66
44
  }
45
+ function nowMs() {
46
+ return Date.now();
47
+ }
48
+ function startNewSession(tsMs) {
49
+ if (typeof window === "undefined") return;
50
+ const sid = `sess_${generateId()}`;
51
+ const startedAt = new Date(tsMs).toISOString();
52
+ cachedSessionId = sid;
53
+ cachedSessionStartAt = startedAt;
54
+ cachedSessionSequence = 0;
55
+ cachedLastActivityAt = tsMs;
56
+ try {
57
+ sessionStorage.setItem(SESSION_ID_KEY, sid);
58
+ sessionStorage.setItem(SESSION_START_AT_KEY, startedAt);
59
+ sessionStorage.setItem(SESSION_SEQUENCE_KEY, "0");
60
+ sessionStorage.setItem(SESSION_LAST_ACTIVITY_AT_KEY, String(tsMs));
61
+ } catch {
62
+ }
63
+ }
64
+ function hydrateSessionFromStorage(tsMs) {
65
+ if (typeof window === "undefined") return;
66
+ if (cachedSessionId && cachedSessionStartAt) {
67
+ const expiredInMemory = !Number.isFinite(cachedLastActivityAt) || cachedLastActivityAt <= 0 || tsMs - cachedLastActivityAt > SESSION_IDLE_TIMEOUT_MS;
68
+ if (expiredInMemory) {
69
+ startNewSession(tsMs);
70
+ }
71
+ return;
72
+ }
73
+ try {
74
+ const sid = sessionStorage.getItem(SESSION_ID_KEY);
75
+ const startedAt = sessionStorage.getItem(SESSION_START_AT_KEY);
76
+ const sequenceRaw = sessionStorage.getItem(SESSION_SEQUENCE_KEY);
77
+ const lastActivityRaw = sessionStorage.getItem(SESSION_LAST_ACTIVITY_AT_KEY);
78
+ const sequence = Number(sequenceRaw || "0");
79
+ const lastActivity = Number(lastActivityRaw || "0");
80
+ if (!sid || !startedAt) {
81
+ startNewSession(tsMs);
82
+ return;
83
+ }
84
+ const expired = !Number.isFinite(lastActivity) || lastActivity <= 0 || tsMs - lastActivity > SESSION_IDLE_TIMEOUT_MS;
85
+ if (expired) {
86
+ startNewSession(tsMs);
87
+ return;
88
+ }
89
+ cachedSessionId = sid;
90
+ cachedSessionStartAt = startedAt;
91
+ cachedSessionSequence = Number.isFinite(sequence) && sequence >= 0 ? sequence : 0;
92
+ cachedLastActivityAt = lastActivity > 0 ? lastActivity : tsMs;
93
+ } catch {
94
+ startNewSession(tsMs);
95
+ }
96
+ }
97
+ function ensureSession(tsMs) {
98
+ if (typeof window === "undefined") return;
99
+ hydrateSessionFromStorage(tsMs);
100
+ if (!cachedSessionId || !cachedSessionStartAt) {
101
+ startNewSession(tsMs);
102
+ }
103
+ }
104
+ function bumpSequence(tsMs) {
105
+ if (typeof window === "undefined") return 0;
106
+ ensureSession(tsMs);
107
+ cachedSessionSequence += 1;
108
+ cachedLastActivityAt = tsMs;
109
+ try {
110
+ sessionStorage.setItem(SESSION_SEQUENCE_KEY, String(cachedSessionSequence));
111
+ sessionStorage.setItem(SESSION_LAST_ACTIVITY_AT_KEY, String(tsMs));
112
+ } catch {
113
+ }
114
+ return cachedSessionSequence;
115
+ }
67
116
  function getDistinctId() {
68
117
  if (cachedDistinctId) return cachedDistinctId;
69
118
  if (typeof window === "undefined") return "server";
@@ -84,23 +133,14 @@ function getDistinctId() {
84
133
  return id;
85
134
  }
86
135
  function getSessionId() {
87
- if (cachedSessionId) return cachedSessionId;
88
136
  if (typeof window === "undefined") return "server";
89
- try {
90
- const stored = sessionStorage.getItem(SESSION_ID_KEY);
91
- if (stored) {
92
- cachedSessionId = stored;
93
- return stored;
94
- }
95
- } catch {
96
- }
97
- const id = `sess_${generateId()}`;
98
- cachedSessionId = id;
99
- try {
100
- sessionStorage.setItem(SESSION_ID_KEY, id);
101
- } catch {
102
- }
103
- return id;
137
+ ensureSession(nowMs());
138
+ return cachedSessionId || "server";
139
+ }
140
+ function getSessionStartAt() {
141
+ if (typeof window === "undefined") return (/* @__PURE__ */ new Date(0)).toISOString();
142
+ ensureSession(nowMs());
143
+ return cachedSessionStartAt || (/* @__PURE__ */ new Date(0)).toISOString();
104
144
  }
105
145
  function getPageKey() {
106
146
  if (typeof window === "undefined") return "";
@@ -115,29 +155,152 @@ function getReferrer() {
115
155
  return document.referrer;
116
156
  }
117
157
  function buildEventContext() {
158
+ const ts = nowMs();
118
159
  return {
119
160
  distinct_id: getDistinctId(),
120
161
  session_id: getSessionId(),
121
162
  $page_url: getPageUrl(),
122
163
  $pathname: typeof window !== "undefined" ? window.location.pathname : "",
123
- $referrer: getReferrer()
164
+ $referrer: getReferrer(),
165
+ $session_sequence: bumpSequence(ts),
166
+ $session_start_at: getSessionStartAt()
124
167
  };
125
168
  }
126
169
 
170
+ // src/utils/eventQueue.ts
171
+ var EventQueue = class {
172
+ constructor(host, apiKey) {
173
+ this.queue = [];
174
+ this.flushTimer = null;
175
+ this.maxBatchSize = 20;
176
+ this.flushIntervalMs = 5e3;
177
+ const normalized = host.replace(/\/$/, "");
178
+ this.endpointBatch = `${normalized}/experiment/metrics/batch`;
179
+ this.endpointSingle = `${normalized}/experiment/metrics`;
180
+ this.apiKey = apiKey;
181
+ }
182
+ enqueue(event) {
183
+ this.queue.push(event);
184
+ if (this.queue.length >= this.maxBatchSize) {
185
+ this.flush();
186
+ return;
187
+ }
188
+ if (!this.flushTimer) {
189
+ this.flushTimer = setTimeout(() => {
190
+ this.flush();
191
+ }, this.flushIntervalMs);
192
+ }
193
+ }
194
+ flush(forceBeacon = false) {
195
+ if (this.flushTimer) {
196
+ clearTimeout(this.flushTimer);
197
+ this.flushTimer = null;
198
+ }
199
+ if (this.queue.length === 0) return;
200
+ const batch = this.queue.splice(0, this.maxBatchSize);
201
+ this.send(batch, forceBeacon);
202
+ if (this.queue.length > 0) {
203
+ this.flushTimer = setTimeout(() => {
204
+ this.flush();
205
+ }, this.flushIntervalMs);
206
+ }
207
+ }
208
+ send(batch, forceBeacon = false) {
209
+ const body = { events: batch };
210
+ const encodedBody = JSON.stringify(body);
211
+ const headers = { "Content-Type": "application/json" };
212
+ if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
213
+ const canBeacon = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && !this.apiKey && (forceBeacon || typeof document !== "undefined" && document.visibilityState === "hidden");
214
+ if (canBeacon) {
215
+ navigator.sendBeacon(this.endpointBatch, encodedBody);
216
+ return;
217
+ }
218
+ try {
219
+ if (batch.length === 1) {
220
+ fetch(this.endpointSingle, {
221
+ method: "POST",
222
+ headers,
223
+ credentials: "include",
224
+ body: JSON.stringify(batch[0]),
225
+ keepalive: true
226
+ }).catch(() => {
227
+ });
228
+ return;
229
+ }
230
+ fetch(this.endpointBatch, {
231
+ method: "POST",
232
+ headers,
233
+ credentials: "include",
234
+ body: encodedBody,
235
+ keepalive: true
236
+ }).catch(() => {
237
+ });
238
+ } catch {
239
+ }
240
+ }
241
+ };
242
+ var queuesByHost = /* @__PURE__ */ new Map();
243
+ var lifecycleListenersInstalled = false;
244
+ function installLifecycleListeners() {
245
+ if (lifecycleListenersInstalled || typeof window === "undefined") return;
246
+ lifecycleListenersInstalled = true;
247
+ const flushWithBeacon = () => {
248
+ for (const queue of queuesByHost.values()) {
249
+ queue.flush(true);
250
+ }
251
+ };
252
+ const flushNormally = () => {
253
+ for (const queue of queuesByHost.values()) {
254
+ queue.flush(false);
255
+ }
256
+ };
257
+ window.addEventListener("pagehide", flushWithBeacon);
258
+ window.addEventListener("beforeunload", flushWithBeacon);
259
+ document.addEventListener("visibilitychange", () => {
260
+ if (document.visibilityState === "hidden") {
261
+ flushWithBeacon();
262
+ } else {
263
+ flushNormally();
264
+ }
265
+ });
266
+ }
267
+ function queueKey(host, apiKey) {
268
+ const normalized = host.replace(/\/$/, "");
269
+ return apiKey ? `${normalized}::${apiKey}` : `${normalized}::__no_key__`;
270
+ }
271
+ function getEventQueue(host, apiKey) {
272
+ const normalized = host.replace(/\/$/, "");
273
+ const key = queueKey(normalized, apiKey);
274
+ let queue = queuesByHost.get(key);
275
+ if (!queue) {
276
+ queue = new EventQueue(normalized, apiKey);
277
+ queuesByHost.set(key, queue);
278
+ }
279
+ installLifecycleListeners();
280
+ return queue;
281
+ }
282
+ function flushEventQueue(host, forceBeacon = false, apiKey) {
283
+ const queue = queuesByHost.get(queueKey(host, apiKey));
284
+ if (!queue) return;
285
+ queue.flush(forceBeacon);
286
+ }
287
+
127
288
  // src/utils/api.ts
128
289
  var pendingDecisions = /* @__PURE__ */ new Map();
129
- async function fetchDecision(host, experimentId, distinctId) {
290
+ async function fetchDecision(host, experimentId, distinctId, apiKey) {
130
291
  const existing = pendingDecisions.get(experimentId);
131
292
  if (existing) return existing;
132
293
  const promise = (async () => {
133
294
  try {
134
295
  const url = `${host.replace(/\/$/, "")}/experiment/decide`;
296
+ const headers = {
297
+ "Content-Type": "application/json",
298
+ Accept: "application/json"
299
+ };
300
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
135
301
  const res = await fetch(url, {
136
302
  method: "POST",
137
- headers: {
138
- "Content-Type": "application/json",
139
- Accept: "application/json"
140
- },
303
+ headers,
141
304
  credentials: "include",
142
305
  body: JSON.stringify({
143
306
  experiment_id: experimentId,
@@ -154,31 +317,35 @@ async function fetchDecision(host, experimentId, distinctId) {
154
317
  pendingDecisions.set(experimentId, promise);
155
318
  return promise;
156
319
  }
157
- function sendMetric(host, event, properties) {
320
+ function sendMetric(host, event, properties, apiKey) {
158
321
  if (typeof window === "undefined") return;
322
+ const environment = detectEnvironment();
159
323
  const ctx = buildEventContext();
160
324
  const payload = {
161
325
  event,
162
- environment: detectEnvironment(),
326
+ environment,
163
327
  properties: {
164
328
  ...ctx,
329
+ environment,
165
330
  source: "react-sdk",
166
331
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
167
332
  ...properties
168
333
  }
169
334
  };
170
- try {
171
- const url = `${host.replace(/\/$/, "")}/experiment/metrics`;
172
- fetch(url, {
173
- method: "POST",
174
- headers: { "Content-Type": "application/json" },
175
- credentials: "include",
176
- body: JSON.stringify(payload)
177
- }).catch(() => {
178
- });
179
- } catch {
335
+ const queuePayload = {
336
+ event: payload.event,
337
+ environment: payload.environment,
338
+ properties: payload.properties
339
+ };
340
+ const queue = getEventQueue(host, apiKey);
341
+ queue.enqueue(queuePayload);
342
+ if (event === "$experiment_exposure" || event === "$experiment_click") {
343
+ queue.flush(false);
180
344
  }
181
345
  }
346
+ function flushMetrics(host, forceBeacon = false, apiKey) {
347
+ flushEventQueue(host, forceBeacon, apiKey);
348
+ }
182
349
  function extractClickMeta(target) {
183
350
  if (!target || !(target instanceof HTMLElement)) return null;
184
351
  const primary = target.closest('[data-probat-click="primary"]');
@@ -198,7 +365,56 @@ function buildMeta(el, isPrimary) {
198
365
  return meta;
199
366
  }
200
367
 
201
- // src/hooks/useExperiment.ts
368
+ // src/context/ProbatContext.tsx
369
+ var ProbatContext = createContext(null);
370
+ var DEFAULT_HOST = "https://api.probat.app";
371
+ function ProbatProvider({
372
+ customerId,
373
+ host = DEFAULT_HOST,
374
+ apiKey,
375
+ bootstrap,
376
+ trackSessionLifecycle = true,
377
+ children
378
+ }) {
379
+ const normalizedHost = host.replace(/\/$/, "");
380
+ useEffect(() => {
381
+ if (!trackSessionLifecycle) return;
382
+ if (typeof window === "undefined") return;
383
+ sendMetric(normalizedHost, "$session_start", {
384
+ ...customerId ? { distinct_id: customerId } : {}
385
+ }, apiKey);
386
+ return () => {
387
+ sendMetric(normalizedHost, "$session_end", {
388
+ ...customerId ? { distinct_id: customerId } : {}
389
+ }, apiKey);
390
+ flushMetrics(normalizedHost, true, apiKey);
391
+ };
392
+ }, [normalizedHost, customerId, trackSessionLifecycle, apiKey]);
393
+ const value = useMemo(
394
+ () => ({
395
+ host: normalizedHost,
396
+ apiKey,
397
+ customerId,
398
+ bootstrap: bootstrap ?? {}
399
+ }),
400
+ [customerId, normalizedHost, apiKey, bootstrap]
401
+ );
402
+ return /* @__PURE__ */ React3.createElement(ProbatContext.Provider, { value }, children);
403
+ }
404
+ function useProbatContext() {
405
+ const ctx = useContext(ProbatContext);
406
+ if (!ctx) {
407
+ throw new Error(
408
+ "useProbatContext must be used within <ProbatProviderClient>. Wrap your app with <ProbatProviderClient>."
409
+ );
410
+ }
411
+ return ctx;
412
+ }
413
+
414
+ // src/components/ProbatProviderClient.tsx
415
+ function ProbatProviderClient(props) {
416
+ return React3.createElement(ProbatProvider, props);
417
+ }
202
418
  var ASSIGNMENT_PREFIX = "probat:assignment:";
203
419
  function readAssignment(id) {
204
420
  if (typeof window === "undefined") return null;
@@ -219,17 +435,33 @@ function writeAssignment(id, variantKey) {
219
435
  } catch {
220
436
  }
221
437
  }
438
+ function readForceParam() {
439
+ if (typeof window === "undefined") return null;
440
+ try {
441
+ return new URLSearchParams(window.location.search).get("__probat_force");
442
+ } catch {
443
+ return null;
444
+ }
445
+ }
222
446
  function useExperiment(id, options = {}) {
223
447
  const { fallback = "control", debug = false } = options;
224
- const { host, bootstrap, customerId } = useProbatContext();
448
+ const { host, bootstrap, customerId, apiKey } = useProbatContext();
225
449
  const [variantKey, setVariantKey] = useState(() => {
450
+ const forced = readForceParam();
451
+ if (forced) return forced;
226
452
  if (bootstrap[id]) return bootstrap[id];
227
453
  return "control";
228
454
  });
229
455
  const [resolved, setResolved] = useState(() => {
230
- return !!bootstrap[id];
456
+ return !!(readForceParam() || bootstrap[id]);
231
457
  });
232
458
  useEffect(() => {
459
+ const forced = readForceParam();
460
+ if (forced) {
461
+ setVariantKey(forced);
462
+ setResolved(true);
463
+ return;
464
+ }
233
465
  if (bootstrap[id] || readAssignment(id)) {
234
466
  const key = bootstrap[id] ?? readAssignment(id) ?? "control";
235
467
  setVariantKey(key);
@@ -240,7 +472,7 @@ function useExperiment(id, options = {}) {
240
472
  (async () => {
241
473
  try {
242
474
  const distinctId = customerId ?? getDistinctId();
243
- const key = await fetchDecision(host, id, distinctId);
475
+ const key = await fetchDecision(host, id, distinctId, apiKey);
244
476
  if (cancelled) return;
245
477
  setVariantKey(key);
246
478
  writeAssignment(id, key);
@@ -359,6 +591,7 @@ function useTrack(options) {
359
591
  const {
360
592
  experimentId,
361
593
  componentInstanceId,
594
+ resolved = true,
362
595
  impression: trackImpression = true,
363
596
  click: trackClick = true,
364
597
  impressionEventName = "$experiment_exposure",
@@ -367,7 +600,7 @@ function useTrack(options) {
367
600
  } = options;
368
601
  const variantKey = options.variantKey ?? void 0;
369
602
  const explicitCustomerId = "customerId" in options ? options.customerId : void 0;
370
- const { host, customerId: providerCustomerId } = useProbatContext();
603
+ const { host, customerId: providerCustomerId, apiKey } = useProbatContext();
371
604
  const resolvedCustomerId = explicitCustomerId ?? providerCustomerId;
372
605
  const isCustomerMode = !variantKey;
373
606
  const autoInstanceId = useStableInstanceId(experimentId);
@@ -392,7 +625,7 @@ function useTrack(options) {
392
625
  );
393
626
  const dedupeVariant = variantKey ?? resolvedCustomerId ?? "__anon__";
394
627
  useEffect(() => {
395
- if (!trackImpression) return;
628
+ if (!trackImpression || !resolved) return;
396
629
  impressionSent.current = false;
397
630
  const pageKey = getPageKey();
398
631
  const dedupeKey = makeDedupeKey(experimentId, dedupeVariant, instanceId, pageKey);
@@ -406,7 +639,7 @@ function useTrack(options) {
406
639
  if (!impressionSent.current) {
407
640
  impressionSent.current = true;
408
641
  markSeen(dedupeKey);
409
- sendMetric(host, impressionEventName, eventProps);
642
+ sendMetric(host, impressionEventName, eventProps, apiKey);
410
643
  if (debug) console.log(`[probat] Impression sent (no IO) for "${experimentId}"`);
411
644
  }
412
645
  return;
@@ -420,7 +653,7 @@ function useTrack(options) {
420
653
  if (impressionSent.current) return;
421
654
  impressionSent.current = true;
422
655
  markSeen(dedupeKey);
423
- sendMetric(host, impressionEventName, eventProps);
656
+ sendMetric(host, impressionEventName, eventProps, apiKey);
424
657
  if (debug) console.log(`[probat] Impression sent for "${experimentId}"`);
425
658
  observer.disconnect();
426
659
  }, 250);
@@ -438,6 +671,7 @@ function useTrack(options) {
438
671
  };
439
672
  }, [
440
673
  trackImpression,
674
+ resolved,
441
675
  experimentId,
442
676
  dedupeVariant,
443
677
  instanceId,
@@ -448,18 +682,18 @@ function useTrack(options) {
448
682
  ]);
449
683
  const handleClick = useCallback(
450
684
  (e) => {
451
- if (!trackClick) return;
685
+ if (!trackClick || !resolved) return;
452
686
  const meta = extractClickMeta(e.target);
453
687
  if (!meta) return;
454
688
  sendMetric(host, clickEventName, {
455
689
  ...eventProps,
456
690
  ...meta
457
- });
691
+ }, apiKey);
458
692
  if (debug) {
459
693
  console.log(`[probat] Click tracked for "${experimentId}"`, meta);
460
694
  }
461
695
  },
462
- [trackClick, host, clickEventName, eventProps, experimentId, debug]
696
+ [trackClick, resolved, host, clickEventName, eventProps, experimentId, debug]
463
697
  );
464
698
  useEffect(() => {
465
699
  const el = containerRef.current;
@@ -493,7 +727,8 @@ function Experiment({
493
727
  experimentId: id,
494
728
  variantKey,
495
729
  componentInstanceId,
496
- impression: resolved ? track?.impression !== false : false,
730
+ resolved,
731
+ impression: track?.impression !== false,
497
732
  click: track?.primaryClick !== false,
498
733
  impressionEventName: track?.impressionEventName,
499
734
  clickEventName: track?.clickEventName,
@@ -531,17 +766,42 @@ function Track({ children, ...trackOptions }) {
531
766
  );
532
767
  }
533
768
  function useProbatMetrics() {
534
- const { host, customerId } = useProbatContext();
769
+ const { host, customerId, apiKey } = useProbatContext();
535
770
  const capture = useCallback(
536
771
  (event, properties = {}) => {
537
772
  sendMetric(host, event, {
538
773
  ...customerId ? { distinct_id: customerId } : {},
539
774
  ...properties
775
+ }, apiKey);
776
+ },
777
+ [host, customerId, apiKey]
778
+ );
779
+ const captureGoal = useCallback(
780
+ (funnelId, funnelStep, properties = {}) => {
781
+ sendMetric(host, "$goal_reached", {
782
+ ...customerId ? { distinct_id: customerId } : {},
783
+ $funnel_id: funnelId,
784
+ $funnel_step: funnelStep,
785
+ ...properties
540
786
  });
541
787
  },
542
788
  [host, customerId]
543
789
  );
544
- return { capture };
790
+ const captureFeatureInteraction = useCallback(
791
+ (interactionName, properties = {}) => {
792
+ sendMetric(host, "$feature_interaction", {
793
+ ...customerId ? { distinct_id: customerId } : {},
794
+ interaction_name: interactionName,
795
+ ...properties
796
+ });
797
+ },
798
+ [host, customerId]
799
+ );
800
+ return {
801
+ capture,
802
+ captureGoal,
803
+ captureFeatureInteraction
804
+ };
545
805
  }
546
806
  function createExperimentContext(experimentId) {
547
807
  const Ctx = createContext(null);
@@ -563,6 +823,6 @@ function createExperimentContext(experimentId) {
563
823
  return { ExperimentProvider, useVariantKey };
564
824
  }
565
825
 
566
- export { Experiment, ProbatProviderClient, Track, createExperimentContext, fetchDecision, sendMetric, useExperiment, useProbatMetrics, useTrack };
826
+ export { Experiment, PROBAT_ENV_DEV, PROBAT_ENV_PROD, ProbatProviderClient, Track, createExperimentContext, fetchDecision, flushMetrics, sendMetric, useExperiment, useProbatMetrics, useTrack };
567
827
  //# sourceMappingURL=index.mjs.map
568
828
  //# sourceMappingURL=index.mjs.map