@probat/react 0.4.5 → 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.d.mts +42 -9
- package/dist/index.d.ts +42 -9
- package/dist/index.js +324 -70
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +322 -71
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
- package/src/__tests__/Experiment.test.tsx +6 -5
- package/src/__tests__/Track.test.tsx +1 -1
- package/src/__tests__/eventQueue.test.ts +68 -0
- package/src/__tests__/setup.ts +55 -0
- package/src/__tests__/useExperiment.test.tsx +3 -2
- package/src/__tests__/useTrack.test.tsx +4 -4
- package/src/__tests__/utils.test.ts +24 -1
- package/src/context/ProbatContext.tsx +27 -5
- package/src/hooks/useExperiment.ts +24 -2
- package/src/hooks/useProbatMetrics.ts +47 -3
- package/src/index.ts +11 -1
- package/src/types/events.ts +48 -0
- package/src/utils/api.ts +30 -18
- package/src/utils/environment.ts +6 -5
- package/src/utils/eventContext.ts +117 -17
- package/src/utils/eventQueue.ts +157 -0
package/dist/index.mjs
CHANGED
|
@@ -2,58 +2,34 @@
|
|
|
2
2
|
"use client";
|
|
3
3
|
import React3, { createContext, useRef, useState, useEffect, useMemo, useCallback, useContext } from 'react';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
var
|
|
7
|
-
|
|
8
|
-
customerId,
|
|
9
|
-
host = DEFAULT_HOST,
|
|
10
|
-
apiKey,
|
|
11
|
-
bootstrap,
|
|
12
|
-
children
|
|
13
|
-
}) {
|
|
14
|
-
const value = useMemo(
|
|
15
|
-
() => ({
|
|
16
|
-
host: host.replace(/\/$/, ""),
|
|
17
|
-
apiKey,
|
|
18
|
-
customerId,
|
|
19
|
-
bootstrap: bootstrap ?? {}
|
|
20
|
-
}),
|
|
21
|
-
[customerId, host, apiKey, bootstrap]
|
|
22
|
-
);
|
|
23
|
-
return /* @__PURE__ */ React3.createElement(ProbatContext.Provider, { value }, children);
|
|
24
|
-
}
|
|
25
|
-
function useProbatContext() {
|
|
26
|
-
const ctx = useContext(ProbatContext);
|
|
27
|
-
if (!ctx) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
"useProbatContext must be used within <ProbatProviderClient>. Wrap your app with <ProbatProviderClient>."
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
return ctx;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/components/ProbatProviderClient.tsx
|
|
36
|
-
function ProbatProviderClient(props) {
|
|
37
|
-
return React3.createElement(ProbatProvider, props);
|
|
38
|
-
}
|
|
5
|
+
// src/types/events.ts
|
|
6
|
+
var PROBAT_ENV_DEV = "dev";
|
|
7
|
+
var PROBAT_ENV_PROD = "prod";
|
|
39
8
|
|
|
40
9
|
// src/utils/environment.ts
|
|
41
10
|
function detectEnvironment() {
|
|
42
11
|
if (typeof window === "undefined") {
|
|
43
|
-
return
|
|
12
|
+
return PROBAT_ENV_PROD;
|
|
44
13
|
}
|
|
45
14
|
const hostname = window.location.hostname;
|
|
46
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.")) {
|
|
47
|
-
return
|
|
16
|
+
return PROBAT_ENV_DEV;
|
|
48
17
|
}
|
|
49
|
-
return
|
|
18
|
+
return PROBAT_ENV_PROD;
|
|
50
19
|
}
|
|
51
20
|
|
|
52
21
|
// src/utils/eventContext.ts
|
|
53
22
|
var DISTINCT_ID_KEY = "probat:distinct_id";
|
|
54
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;
|
|
55
28
|
var cachedDistinctId = null;
|
|
56
29
|
var cachedSessionId = null;
|
|
30
|
+
var cachedSessionStartAt = null;
|
|
31
|
+
var cachedSessionSequence = 0;
|
|
32
|
+
var cachedLastActivityAt = 0;
|
|
57
33
|
function generateId() {
|
|
58
34
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
59
35
|
return crypto.randomUUID();
|
|
@@ -66,6 +42,77 @@ function generateId() {
|
|
|
66
42
|
}
|
|
67
43
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
68
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
|
+
}
|
|
69
116
|
function getDistinctId() {
|
|
70
117
|
if (cachedDistinctId) return cachedDistinctId;
|
|
71
118
|
if (typeof window === "undefined") return "server";
|
|
@@ -86,23 +133,14 @@ function getDistinctId() {
|
|
|
86
133
|
return id;
|
|
87
134
|
}
|
|
88
135
|
function getSessionId() {
|
|
89
|
-
if (cachedSessionId) return cachedSessionId;
|
|
90
136
|
if (typeof window === "undefined") return "server";
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
const id = `sess_${generateId()}`;
|
|
100
|
-
cachedSessionId = id;
|
|
101
|
-
try {
|
|
102
|
-
sessionStorage.setItem(SESSION_ID_KEY, id);
|
|
103
|
-
} catch {
|
|
104
|
-
}
|
|
105
|
-
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();
|
|
106
144
|
}
|
|
107
145
|
function getPageKey() {
|
|
108
146
|
if (typeof window === "undefined") return "";
|
|
@@ -117,13 +155,134 @@ function getReferrer() {
|
|
|
117
155
|
return document.referrer;
|
|
118
156
|
}
|
|
119
157
|
function buildEventContext() {
|
|
158
|
+
const ts = nowMs();
|
|
120
159
|
return {
|
|
121
160
|
distinct_id: getDistinctId(),
|
|
122
161
|
session_id: getSessionId(),
|
|
123
162
|
$page_url: getPageUrl(),
|
|
124
163
|
$pathname: typeof window !== "undefined" ? window.location.pathname : "",
|
|
125
|
-
$referrer: getReferrer()
|
|
164
|
+
$referrer: getReferrer(),
|
|
165
|
+
$session_sequence: bumpSequence(ts),
|
|
166
|
+
$session_start_at: getSessionStartAt()
|
|
167
|
+
};
|
|
168
|
+
}
|
|
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
|
+
}
|
|
126
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);
|
|
127
286
|
}
|
|
128
287
|
|
|
129
288
|
// src/utils/api.ts
|
|
@@ -160,31 +319,33 @@ async function fetchDecision(host, experimentId, distinctId, apiKey) {
|
|
|
160
319
|
}
|
|
161
320
|
function sendMetric(host, event, properties, apiKey) {
|
|
162
321
|
if (typeof window === "undefined") return;
|
|
322
|
+
const environment = detectEnvironment();
|
|
163
323
|
const ctx = buildEventContext();
|
|
164
324
|
const payload = {
|
|
165
325
|
event,
|
|
166
|
-
environment
|
|
326
|
+
environment,
|
|
167
327
|
properties: {
|
|
168
328
|
...ctx,
|
|
329
|
+
environment,
|
|
169
330
|
source: "react-sdk",
|
|
170
331
|
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
171
332
|
...properties
|
|
172
333
|
}
|
|
173
334
|
};
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}).catch(() => {
|
|
184
|
-
});
|
|
185
|
-
} 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);
|
|
186
344
|
}
|
|
187
345
|
}
|
|
346
|
+
function flushMetrics(host, forceBeacon = false, apiKey) {
|
|
347
|
+
flushEventQueue(host, forceBeacon, apiKey);
|
|
348
|
+
}
|
|
188
349
|
function extractClickMeta(target) {
|
|
189
350
|
if (!target || !(target instanceof HTMLElement)) return null;
|
|
190
351
|
const primary = target.closest('[data-probat-click="primary"]');
|
|
@@ -204,7 +365,56 @@ function buildMeta(el, isPrimary) {
|
|
|
204
365
|
return meta;
|
|
205
366
|
}
|
|
206
367
|
|
|
207
|
-
// src/
|
|
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
|
+
}
|
|
208
418
|
var ASSIGNMENT_PREFIX = "probat:assignment:";
|
|
209
419
|
function readAssignment(id) {
|
|
210
420
|
if (typeof window === "undefined") return null;
|
|
@@ -225,17 +435,33 @@ function writeAssignment(id, variantKey) {
|
|
|
225
435
|
} catch {
|
|
226
436
|
}
|
|
227
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
|
+
}
|
|
228
446
|
function useExperiment(id, options = {}) {
|
|
229
447
|
const { fallback = "control", debug = false } = options;
|
|
230
448
|
const { host, bootstrap, customerId, apiKey } = useProbatContext();
|
|
231
449
|
const [variantKey, setVariantKey] = useState(() => {
|
|
450
|
+
const forced = readForceParam();
|
|
451
|
+
if (forced) return forced;
|
|
232
452
|
if (bootstrap[id]) return bootstrap[id];
|
|
233
453
|
return "control";
|
|
234
454
|
});
|
|
235
455
|
const [resolved, setResolved] = useState(() => {
|
|
236
|
-
return !!bootstrap[id];
|
|
456
|
+
return !!(readForceParam() || bootstrap[id]);
|
|
237
457
|
});
|
|
238
458
|
useEffect(() => {
|
|
459
|
+
const forced = readForceParam();
|
|
460
|
+
if (forced) {
|
|
461
|
+
setVariantKey(forced);
|
|
462
|
+
setResolved(true);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
239
465
|
if (bootstrap[id] || readAssignment(id)) {
|
|
240
466
|
const key = bootstrap[id] ?? readAssignment(id) ?? "control";
|
|
241
467
|
setVariantKey(key);
|
|
@@ -550,7 +776,32 @@ function useProbatMetrics() {
|
|
|
550
776
|
},
|
|
551
777
|
[host, customerId, apiKey]
|
|
552
778
|
);
|
|
553
|
-
|
|
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
|
|
786
|
+
});
|
|
787
|
+
},
|
|
788
|
+
[host, customerId]
|
|
789
|
+
);
|
|
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
|
+
};
|
|
554
805
|
}
|
|
555
806
|
function createExperimentContext(experimentId) {
|
|
556
807
|
const Ctx = createContext(null);
|
|
@@ -572,6 +823,6 @@ function createExperimentContext(experimentId) {
|
|
|
572
823
|
return { ExperimentProvider, useVariantKey };
|
|
573
824
|
}
|
|
574
825
|
|
|
575
|
-
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 };
|
|
576
827
|
//# sourceMappingURL=index.mjs.map
|
|
577
828
|
//# sourceMappingURL=index.mjs.map
|