@probat/react 0.4.5 → 0.4.7
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 +327 -70
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +325 -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 +28 -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,15 +155,136 @@ 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()
|
|
126
167
|
};
|
|
127
168
|
}
|
|
128
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
|
+
|
|
129
288
|
// src/utils/api.ts
|
|
130
289
|
var pendingDecisions = /* @__PURE__ */ new Map();
|
|
131
290
|
async function fetchDecision(host, experimentId, distinctId, apiKey) {
|
|
@@ -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,36 @@ function writeAssignment(id, variantKey) {
|
|
|
225
435
|
} catch {
|
|
226
436
|
}
|
|
227
437
|
}
|
|
438
|
+
function readForceParam(id) {
|
|
439
|
+
if (typeof window === "undefined") return null;
|
|
440
|
+
try {
|
|
441
|
+
const raw = new URLSearchParams(window.location.search).get("__probat_force");
|
|
442
|
+
if (!raw) return null;
|
|
443
|
+
const [forceId, forceKey] = raw.split(":");
|
|
444
|
+
return forceId === id ? forceKey ?? null : null;
|
|
445
|
+
} catch {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
228
449
|
function useExperiment(id, options = {}) {
|
|
229
450
|
const { fallback = "control", debug = false } = options;
|
|
230
451
|
const { host, bootstrap, customerId, apiKey } = useProbatContext();
|
|
231
452
|
const [variantKey, setVariantKey] = useState(() => {
|
|
453
|
+
const forced = readForceParam(id);
|
|
454
|
+
if (forced) return forced;
|
|
232
455
|
if (bootstrap[id]) return bootstrap[id];
|
|
233
456
|
return "control";
|
|
234
457
|
});
|
|
235
458
|
const [resolved, setResolved] = useState(() => {
|
|
236
|
-
return !!bootstrap[id];
|
|
459
|
+
return !!(readForceParam(id) || bootstrap[id]);
|
|
237
460
|
});
|
|
238
461
|
useEffect(() => {
|
|
462
|
+
const forced = readForceParam(id);
|
|
463
|
+
if (forced) {
|
|
464
|
+
setVariantKey(forced);
|
|
465
|
+
setResolved(true);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
239
468
|
if (bootstrap[id] || readAssignment(id)) {
|
|
240
469
|
const key = bootstrap[id] ?? readAssignment(id) ?? "control";
|
|
241
470
|
setVariantKey(key);
|
|
@@ -550,7 +779,32 @@ function useProbatMetrics() {
|
|
|
550
779
|
},
|
|
551
780
|
[host, customerId, apiKey]
|
|
552
781
|
);
|
|
553
|
-
|
|
782
|
+
const captureGoal = useCallback(
|
|
783
|
+
(funnelId, funnelStep, properties = {}) => {
|
|
784
|
+
sendMetric(host, "$goal_reached", {
|
|
785
|
+
...customerId ? { distinct_id: customerId } : {},
|
|
786
|
+
$funnel_id: funnelId,
|
|
787
|
+
$funnel_step: funnelStep,
|
|
788
|
+
...properties
|
|
789
|
+
});
|
|
790
|
+
},
|
|
791
|
+
[host, customerId]
|
|
792
|
+
);
|
|
793
|
+
const captureFeatureInteraction = useCallback(
|
|
794
|
+
(interactionName, properties = {}) => {
|
|
795
|
+
sendMetric(host, "$feature_interaction", {
|
|
796
|
+
...customerId ? { distinct_id: customerId } : {},
|
|
797
|
+
interaction_name: interactionName,
|
|
798
|
+
...properties
|
|
799
|
+
});
|
|
800
|
+
},
|
|
801
|
+
[host, customerId]
|
|
802
|
+
);
|
|
803
|
+
return {
|
|
804
|
+
capture,
|
|
805
|
+
captureGoal,
|
|
806
|
+
captureFeatureInteraction
|
|
807
|
+
};
|
|
554
808
|
}
|
|
555
809
|
function createExperimentContext(experimentId) {
|
|
556
810
|
const Ctx = createContext(null);
|
|
@@ -572,6 +826,6 @@ function createExperimentContext(experimentId) {
|
|
|
572
826
|
return { ExperimentProvider, useVariantKey };
|
|
573
827
|
}
|
|
574
828
|
|
|
575
|
-
export { Experiment, ProbatProviderClient, Track, createExperimentContext, fetchDecision, sendMetric, useExperiment, useProbatMetrics, useTrack };
|
|
829
|
+
export { Experiment, PROBAT_ENV_DEV, PROBAT_ENV_PROD, ProbatProviderClient, Track, createExperimentContext, fetchDecision, flushMetrics, sendMetric, useExperiment, useProbatMetrics, useTrack };
|
|
576
830
|
//# sourceMappingURL=index.mjs.map
|
|
577
831
|
//# sourceMappingURL=index.mjs.map
|