@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.js
CHANGED
|
@@ -8,58 +8,34 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
|
8
8
|
|
|
9
9
|
var React3__default = /*#__PURE__*/_interopDefault(React3);
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
var
|
|
13
|
-
|
|
14
|
-
customerId,
|
|
15
|
-
host = DEFAULT_HOST,
|
|
16
|
-
apiKey,
|
|
17
|
-
bootstrap,
|
|
18
|
-
children
|
|
19
|
-
}) {
|
|
20
|
-
const value = React3.useMemo(
|
|
21
|
-
() => ({
|
|
22
|
-
host: host.replace(/\/$/, ""),
|
|
23
|
-
apiKey,
|
|
24
|
-
customerId,
|
|
25
|
-
bootstrap: bootstrap ?? {}
|
|
26
|
-
}),
|
|
27
|
-
[customerId, host, apiKey, bootstrap]
|
|
28
|
-
);
|
|
29
|
-
return /* @__PURE__ */ React3__default.default.createElement(ProbatContext.Provider, { value }, children);
|
|
30
|
-
}
|
|
31
|
-
function useProbatContext() {
|
|
32
|
-
const ctx = React3.useContext(ProbatContext);
|
|
33
|
-
if (!ctx) {
|
|
34
|
-
throw new Error(
|
|
35
|
-
"useProbatContext must be used within <ProbatProviderClient>. Wrap your app with <ProbatProviderClient>."
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
return ctx;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// src/components/ProbatProviderClient.tsx
|
|
42
|
-
function ProbatProviderClient(props) {
|
|
43
|
-
return React3__default.default.createElement(ProbatProvider, props);
|
|
44
|
-
}
|
|
11
|
+
// src/types/events.ts
|
|
12
|
+
var PROBAT_ENV_DEV = "dev";
|
|
13
|
+
var PROBAT_ENV_PROD = "prod";
|
|
45
14
|
|
|
46
15
|
// src/utils/environment.ts
|
|
47
16
|
function detectEnvironment() {
|
|
48
17
|
if (typeof window === "undefined") {
|
|
49
|
-
return
|
|
18
|
+
return PROBAT_ENV_PROD;
|
|
50
19
|
}
|
|
51
20
|
const hostname = window.location.hostname;
|
|
52
21
|
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.")) {
|
|
53
|
-
return
|
|
22
|
+
return PROBAT_ENV_DEV;
|
|
54
23
|
}
|
|
55
|
-
return
|
|
24
|
+
return PROBAT_ENV_PROD;
|
|
56
25
|
}
|
|
57
26
|
|
|
58
27
|
// src/utils/eventContext.ts
|
|
59
28
|
var DISTINCT_ID_KEY = "probat:distinct_id";
|
|
60
29
|
var SESSION_ID_KEY = "probat:session_id";
|
|
30
|
+
var SESSION_START_AT_KEY = "probat:session_start_at";
|
|
31
|
+
var SESSION_SEQUENCE_KEY = "probat:session_sequence";
|
|
32
|
+
var SESSION_LAST_ACTIVITY_AT_KEY = "probat:session_last_activity_at";
|
|
33
|
+
var SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
61
34
|
var cachedDistinctId = null;
|
|
62
35
|
var cachedSessionId = null;
|
|
36
|
+
var cachedSessionStartAt = null;
|
|
37
|
+
var cachedSessionSequence = 0;
|
|
38
|
+
var cachedLastActivityAt = 0;
|
|
63
39
|
function generateId() {
|
|
64
40
|
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
65
41
|
return crypto.randomUUID();
|
|
@@ -72,6 +48,77 @@ function generateId() {
|
|
|
72
48
|
}
|
|
73
49
|
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
74
50
|
}
|
|
51
|
+
function nowMs() {
|
|
52
|
+
return Date.now();
|
|
53
|
+
}
|
|
54
|
+
function startNewSession(tsMs) {
|
|
55
|
+
if (typeof window === "undefined") return;
|
|
56
|
+
const sid = `sess_${generateId()}`;
|
|
57
|
+
const startedAt = new Date(tsMs).toISOString();
|
|
58
|
+
cachedSessionId = sid;
|
|
59
|
+
cachedSessionStartAt = startedAt;
|
|
60
|
+
cachedSessionSequence = 0;
|
|
61
|
+
cachedLastActivityAt = tsMs;
|
|
62
|
+
try {
|
|
63
|
+
sessionStorage.setItem(SESSION_ID_KEY, sid);
|
|
64
|
+
sessionStorage.setItem(SESSION_START_AT_KEY, startedAt);
|
|
65
|
+
sessionStorage.setItem(SESSION_SEQUENCE_KEY, "0");
|
|
66
|
+
sessionStorage.setItem(SESSION_LAST_ACTIVITY_AT_KEY, String(tsMs));
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function hydrateSessionFromStorage(tsMs) {
|
|
71
|
+
if (typeof window === "undefined") return;
|
|
72
|
+
if (cachedSessionId && cachedSessionStartAt) {
|
|
73
|
+
const expiredInMemory = !Number.isFinite(cachedLastActivityAt) || cachedLastActivityAt <= 0 || tsMs - cachedLastActivityAt > SESSION_IDLE_TIMEOUT_MS;
|
|
74
|
+
if (expiredInMemory) {
|
|
75
|
+
startNewSession(tsMs);
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const sid = sessionStorage.getItem(SESSION_ID_KEY);
|
|
81
|
+
const startedAt = sessionStorage.getItem(SESSION_START_AT_KEY);
|
|
82
|
+
const sequenceRaw = sessionStorage.getItem(SESSION_SEQUENCE_KEY);
|
|
83
|
+
const lastActivityRaw = sessionStorage.getItem(SESSION_LAST_ACTIVITY_AT_KEY);
|
|
84
|
+
const sequence = Number(sequenceRaw || "0");
|
|
85
|
+
const lastActivity = Number(lastActivityRaw || "0");
|
|
86
|
+
if (!sid || !startedAt) {
|
|
87
|
+
startNewSession(tsMs);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const expired = !Number.isFinite(lastActivity) || lastActivity <= 0 || tsMs - lastActivity > SESSION_IDLE_TIMEOUT_MS;
|
|
91
|
+
if (expired) {
|
|
92
|
+
startNewSession(tsMs);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
cachedSessionId = sid;
|
|
96
|
+
cachedSessionStartAt = startedAt;
|
|
97
|
+
cachedSessionSequence = Number.isFinite(sequence) && sequence >= 0 ? sequence : 0;
|
|
98
|
+
cachedLastActivityAt = lastActivity > 0 ? lastActivity : tsMs;
|
|
99
|
+
} catch {
|
|
100
|
+
startNewSession(tsMs);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function ensureSession(tsMs) {
|
|
104
|
+
if (typeof window === "undefined") return;
|
|
105
|
+
hydrateSessionFromStorage(tsMs);
|
|
106
|
+
if (!cachedSessionId || !cachedSessionStartAt) {
|
|
107
|
+
startNewSession(tsMs);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function bumpSequence(tsMs) {
|
|
111
|
+
if (typeof window === "undefined") return 0;
|
|
112
|
+
ensureSession(tsMs);
|
|
113
|
+
cachedSessionSequence += 1;
|
|
114
|
+
cachedLastActivityAt = tsMs;
|
|
115
|
+
try {
|
|
116
|
+
sessionStorage.setItem(SESSION_SEQUENCE_KEY, String(cachedSessionSequence));
|
|
117
|
+
sessionStorage.setItem(SESSION_LAST_ACTIVITY_AT_KEY, String(tsMs));
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
return cachedSessionSequence;
|
|
121
|
+
}
|
|
75
122
|
function getDistinctId() {
|
|
76
123
|
if (cachedDistinctId) return cachedDistinctId;
|
|
77
124
|
if (typeof window === "undefined") return "server";
|
|
@@ -92,23 +139,14 @@ function getDistinctId() {
|
|
|
92
139
|
return id;
|
|
93
140
|
}
|
|
94
141
|
function getSessionId() {
|
|
95
|
-
if (cachedSessionId) return cachedSessionId;
|
|
96
142
|
if (typeof window === "undefined") return "server";
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
const id = `sess_${generateId()}`;
|
|
106
|
-
cachedSessionId = id;
|
|
107
|
-
try {
|
|
108
|
-
sessionStorage.setItem(SESSION_ID_KEY, id);
|
|
109
|
-
} catch {
|
|
110
|
-
}
|
|
111
|
-
return id;
|
|
143
|
+
ensureSession(nowMs());
|
|
144
|
+
return cachedSessionId || "server";
|
|
145
|
+
}
|
|
146
|
+
function getSessionStartAt() {
|
|
147
|
+
if (typeof window === "undefined") return (/* @__PURE__ */ new Date(0)).toISOString();
|
|
148
|
+
ensureSession(nowMs());
|
|
149
|
+
return cachedSessionStartAt || (/* @__PURE__ */ new Date(0)).toISOString();
|
|
112
150
|
}
|
|
113
151
|
function getPageKey() {
|
|
114
152
|
if (typeof window === "undefined") return "";
|
|
@@ -123,15 +161,136 @@ function getReferrer() {
|
|
|
123
161
|
return document.referrer;
|
|
124
162
|
}
|
|
125
163
|
function buildEventContext() {
|
|
164
|
+
const ts = nowMs();
|
|
126
165
|
return {
|
|
127
166
|
distinct_id: getDistinctId(),
|
|
128
167
|
session_id: getSessionId(),
|
|
129
168
|
$page_url: getPageUrl(),
|
|
130
169
|
$pathname: typeof window !== "undefined" ? window.location.pathname : "",
|
|
131
|
-
$referrer: getReferrer()
|
|
170
|
+
$referrer: getReferrer(),
|
|
171
|
+
$session_sequence: bumpSequence(ts),
|
|
172
|
+
$session_start_at: getSessionStartAt()
|
|
132
173
|
};
|
|
133
174
|
}
|
|
134
175
|
|
|
176
|
+
// src/utils/eventQueue.ts
|
|
177
|
+
var EventQueue = class {
|
|
178
|
+
constructor(host, apiKey) {
|
|
179
|
+
this.queue = [];
|
|
180
|
+
this.flushTimer = null;
|
|
181
|
+
this.maxBatchSize = 20;
|
|
182
|
+
this.flushIntervalMs = 5e3;
|
|
183
|
+
const normalized = host.replace(/\/$/, "");
|
|
184
|
+
this.endpointBatch = `${normalized}/experiment/metrics/batch`;
|
|
185
|
+
this.endpointSingle = `${normalized}/experiment/metrics`;
|
|
186
|
+
this.apiKey = apiKey;
|
|
187
|
+
}
|
|
188
|
+
enqueue(event) {
|
|
189
|
+
this.queue.push(event);
|
|
190
|
+
if (this.queue.length >= this.maxBatchSize) {
|
|
191
|
+
this.flush();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (!this.flushTimer) {
|
|
195
|
+
this.flushTimer = setTimeout(() => {
|
|
196
|
+
this.flush();
|
|
197
|
+
}, this.flushIntervalMs);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
flush(forceBeacon = false) {
|
|
201
|
+
if (this.flushTimer) {
|
|
202
|
+
clearTimeout(this.flushTimer);
|
|
203
|
+
this.flushTimer = null;
|
|
204
|
+
}
|
|
205
|
+
if (this.queue.length === 0) return;
|
|
206
|
+
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
207
|
+
this.send(batch, forceBeacon);
|
|
208
|
+
if (this.queue.length > 0) {
|
|
209
|
+
this.flushTimer = setTimeout(() => {
|
|
210
|
+
this.flush();
|
|
211
|
+
}, this.flushIntervalMs);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
send(batch, forceBeacon = false) {
|
|
215
|
+
const body = { events: batch };
|
|
216
|
+
const encodedBody = JSON.stringify(body);
|
|
217
|
+
const headers = { "Content-Type": "application/json" };
|
|
218
|
+
if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
|
|
219
|
+
const canBeacon = typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function" && !this.apiKey && (forceBeacon || typeof document !== "undefined" && document.visibilityState === "hidden");
|
|
220
|
+
if (canBeacon) {
|
|
221
|
+
navigator.sendBeacon(this.endpointBatch, encodedBody);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
if (batch.length === 1) {
|
|
226
|
+
fetch(this.endpointSingle, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers,
|
|
229
|
+
credentials: "include",
|
|
230
|
+
body: JSON.stringify(batch[0]),
|
|
231
|
+
keepalive: true
|
|
232
|
+
}).catch(() => {
|
|
233
|
+
});
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
fetch(this.endpointBatch, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
headers,
|
|
239
|
+
credentials: "include",
|
|
240
|
+
body: encodedBody,
|
|
241
|
+
keepalive: true
|
|
242
|
+
}).catch(() => {
|
|
243
|
+
});
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
var queuesByHost = /* @__PURE__ */ new Map();
|
|
249
|
+
var lifecycleListenersInstalled = false;
|
|
250
|
+
function installLifecycleListeners() {
|
|
251
|
+
if (lifecycleListenersInstalled || typeof window === "undefined") return;
|
|
252
|
+
lifecycleListenersInstalled = true;
|
|
253
|
+
const flushWithBeacon = () => {
|
|
254
|
+
for (const queue of queuesByHost.values()) {
|
|
255
|
+
queue.flush(true);
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
const flushNormally = () => {
|
|
259
|
+
for (const queue of queuesByHost.values()) {
|
|
260
|
+
queue.flush(false);
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
window.addEventListener("pagehide", flushWithBeacon);
|
|
264
|
+
window.addEventListener("beforeunload", flushWithBeacon);
|
|
265
|
+
document.addEventListener("visibilitychange", () => {
|
|
266
|
+
if (document.visibilityState === "hidden") {
|
|
267
|
+
flushWithBeacon();
|
|
268
|
+
} else {
|
|
269
|
+
flushNormally();
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
function queueKey(host, apiKey) {
|
|
274
|
+
const normalized = host.replace(/\/$/, "");
|
|
275
|
+
return apiKey ? `${normalized}::${apiKey}` : `${normalized}::__no_key__`;
|
|
276
|
+
}
|
|
277
|
+
function getEventQueue(host, apiKey) {
|
|
278
|
+
const normalized = host.replace(/\/$/, "");
|
|
279
|
+
const key = queueKey(normalized, apiKey);
|
|
280
|
+
let queue = queuesByHost.get(key);
|
|
281
|
+
if (!queue) {
|
|
282
|
+
queue = new EventQueue(normalized, apiKey);
|
|
283
|
+
queuesByHost.set(key, queue);
|
|
284
|
+
}
|
|
285
|
+
installLifecycleListeners();
|
|
286
|
+
return queue;
|
|
287
|
+
}
|
|
288
|
+
function flushEventQueue(host, forceBeacon = false, apiKey) {
|
|
289
|
+
const queue = queuesByHost.get(queueKey(host, apiKey));
|
|
290
|
+
if (!queue) return;
|
|
291
|
+
queue.flush(forceBeacon);
|
|
292
|
+
}
|
|
293
|
+
|
|
135
294
|
// src/utils/api.ts
|
|
136
295
|
var pendingDecisions = /* @__PURE__ */ new Map();
|
|
137
296
|
async function fetchDecision(host, experimentId, distinctId, apiKey) {
|
|
@@ -166,31 +325,33 @@ async function fetchDecision(host, experimentId, distinctId, apiKey) {
|
|
|
166
325
|
}
|
|
167
326
|
function sendMetric(host, event, properties, apiKey) {
|
|
168
327
|
if (typeof window === "undefined") return;
|
|
328
|
+
const environment = detectEnvironment();
|
|
169
329
|
const ctx = buildEventContext();
|
|
170
330
|
const payload = {
|
|
171
331
|
event,
|
|
172
|
-
environment
|
|
332
|
+
environment,
|
|
173
333
|
properties: {
|
|
174
334
|
...ctx,
|
|
335
|
+
environment,
|
|
175
336
|
source: "react-sdk",
|
|
176
337
|
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
177
338
|
...properties
|
|
178
339
|
}
|
|
179
340
|
};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}).catch(() => {
|
|
190
|
-
});
|
|
191
|
-
} catch {
|
|
341
|
+
const queuePayload = {
|
|
342
|
+
event: payload.event,
|
|
343
|
+
environment: payload.environment,
|
|
344
|
+
properties: payload.properties
|
|
345
|
+
};
|
|
346
|
+
const queue = getEventQueue(host, apiKey);
|
|
347
|
+
queue.enqueue(queuePayload);
|
|
348
|
+
if (event === "$experiment_exposure" || event === "$experiment_click") {
|
|
349
|
+
queue.flush(false);
|
|
192
350
|
}
|
|
193
351
|
}
|
|
352
|
+
function flushMetrics(host, forceBeacon = false, apiKey) {
|
|
353
|
+
flushEventQueue(host, forceBeacon, apiKey);
|
|
354
|
+
}
|
|
194
355
|
function extractClickMeta(target) {
|
|
195
356
|
if (!target || !(target instanceof HTMLElement)) return null;
|
|
196
357
|
const primary = target.closest('[data-probat-click="primary"]');
|
|
@@ -210,7 +371,56 @@ function buildMeta(el, isPrimary) {
|
|
|
210
371
|
return meta;
|
|
211
372
|
}
|
|
212
373
|
|
|
213
|
-
// src/
|
|
374
|
+
// src/context/ProbatContext.tsx
|
|
375
|
+
var ProbatContext = React3.createContext(null);
|
|
376
|
+
var DEFAULT_HOST = "https://api.probat.app";
|
|
377
|
+
function ProbatProvider({
|
|
378
|
+
customerId,
|
|
379
|
+
host = DEFAULT_HOST,
|
|
380
|
+
apiKey,
|
|
381
|
+
bootstrap,
|
|
382
|
+
trackSessionLifecycle = true,
|
|
383
|
+
children
|
|
384
|
+
}) {
|
|
385
|
+
const normalizedHost = host.replace(/\/$/, "");
|
|
386
|
+
React3.useEffect(() => {
|
|
387
|
+
if (!trackSessionLifecycle) return;
|
|
388
|
+
if (typeof window === "undefined") return;
|
|
389
|
+
sendMetric(normalizedHost, "$session_start", {
|
|
390
|
+
...customerId ? { distinct_id: customerId } : {}
|
|
391
|
+
}, apiKey);
|
|
392
|
+
return () => {
|
|
393
|
+
sendMetric(normalizedHost, "$session_end", {
|
|
394
|
+
...customerId ? { distinct_id: customerId } : {}
|
|
395
|
+
}, apiKey);
|
|
396
|
+
flushMetrics(normalizedHost, true, apiKey);
|
|
397
|
+
};
|
|
398
|
+
}, [normalizedHost, customerId, trackSessionLifecycle, apiKey]);
|
|
399
|
+
const value = React3.useMemo(
|
|
400
|
+
() => ({
|
|
401
|
+
host: normalizedHost,
|
|
402
|
+
apiKey,
|
|
403
|
+
customerId,
|
|
404
|
+
bootstrap: bootstrap ?? {}
|
|
405
|
+
}),
|
|
406
|
+
[customerId, normalizedHost, apiKey, bootstrap]
|
|
407
|
+
);
|
|
408
|
+
return /* @__PURE__ */ React3__default.default.createElement(ProbatContext.Provider, { value }, children);
|
|
409
|
+
}
|
|
410
|
+
function useProbatContext() {
|
|
411
|
+
const ctx = React3.useContext(ProbatContext);
|
|
412
|
+
if (!ctx) {
|
|
413
|
+
throw new Error(
|
|
414
|
+
"useProbatContext must be used within <ProbatProviderClient>. Wrap your app with <ProbatProviderClient>."
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
return ctx;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/components/ProbatProviderClient.tsx
|
|
421
|
+
function ProbatProviderClient(props) {
|
|
422
|
+
return React3__default.default.createElement(ProbatProvider, props);
|
|
423
|
+
}
|
|
214
424
|
var ASSIGNMENT_PREFIX = "probat:assignment:";
|
|
215
425
|
function readAssignment(id) {
|
|
216
426
|
if (typeof window === "undefined") return null;
|
|
@@ -231,17 +441,36 @@ function writeAssignment(id, variantKey) {
|
|
|
231
441
|
} catch {
|
|
232
442
|
}
|
|
233
443
|
}
|
|
444
|
+
function readForceParam(id) {
|
|
445
|
+
if (typeof window === "undefined") return null;
|
|
446
|
+
try {
|
|
447
|
+
const raw = new URLSearchParams(window.location.search).get("__probat_force");
|
|
448
|
+
if (!raw) return null;
|
|
449
|
+
const [forceId, forceKey] = raw.split(":");
|
|
450
|
+
return forceId === id ? forceKey ?? null : null;
|
|
451
|
+
} catch {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
234
455
|
function useExperiment(id, options = {}) {
|
|
235
456
|
const { fallback = "control", debug = false } = options;
|
|
236
457
|
const { host, bootstrap, customerId, apiKey } = useProbatContext();
|
|
237
458
|
const [variantKey, setVariantKey] = React3.useState(() => {
|
|
459
|
+
const forced = readForceParam(id);
|
|
460
|
+
if (forced) return forced;
|
|
238
461
|
if (bootstrap[id]) return bootstrap[id];
|
|
239
462
|
return "control";
|
|
240
463
|
});
|
|
241
464
|
const [resolved, setResolved] = React3.useState(() => {
|
|
242
|
-
return !!bootstrap[id];
|
|
465
|
+
return !!(readForceParam(id) || bootstrap[id]);
|
|
243
466
|
});
|
|
244
467
|
React3.useEffect(() => {
|
|
468
|
+
const forced = readForceParam(id);
|
|
469
|
+
if (forced) {
|
|
470
|
+
setVariantKey(forced);
|
|
471
|
+
setResolved(true);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
245
474
|
if (bootstrap[id] || readAssignment(id)) {
|
|
246
475
|
const key = bootstrap[id] ?? readAssignment(id) ?? "control";
|
|
247
476
|
setVariantKey(key);
|
|
@@ -556,7 +785,32 @@ function useProbatMetrics() {
|
|
|
556
785
|
},
|
|
557
786
|
[host, customerId, apiKey]
|
|
558
787
|
);
|
|
559
|
-
|
|
788
|
+
const captureGoal = React3.useCallback(
|
|
789
|
+
(funnelId, funnelStep, properties = {}) => {
|
|
790
|
+
sendMetric(host, "$goal_reached", {
|
|
791
|
+
...customerId ? { distinct_id: customerId } : {},
|
|
792
|
+
$funnel_id: funnelId,
|
|
793
|
+
$funnel_step: funnelStep,
|
|
794
|
+
...properties
|
|
795
|
+
});
|
|
796
|
+
},
|
|
797
|
+
[host, customerId]
|
|
798
|
+
);
|
|
799
|
+
const captureFeatureInteraction = React3.useCallback(
|
|
800
|
+
(interactionName, properties = {}) => {
|
|
801
|
+
sendMetric(host, "$feature_interaction", {
|
|
802
|
+
...customerId ? { distinct_id: customerId } : {},
|
|
803
|
+
interaction_name: interactionName,
|
|
804
|
+
...properties
|
|
805
|
+
});
|
|
806
|
+
},
|
|
807
|
+
[host, customerId]
|
|
808
|
+
);
|
|
809
|
+
return {
|
|
810
|
+
capture,
|
|
811
|
+
captureGoal,
|
|
812
|
+
captureFeatureInteraction
|
|
813
|
+
};
|
|
560
814
|
}
|
|
561
815
|
function createExperimentContext(experimentId) {
|
|
562
816
|
const Ctx = React3.createContext(null);
|
|
@@ -579,10 +833,13 @@ function createExperimentContext(experimentId) {
|
|
|
579
833
|
}
|
|
580
834
|
|
|
581
835
|
exports.Experiment = Experiment;
|
|
836
|
+
exports.PROBAT_ENV_DEV = PROBAT_ENV_DEV;
|
|
837
|
+
exports.PROBAT_ENV_PROD = PROBAT_ENV_PROD;
|
|
582
838
|
exports.ProbatProviderClient = ProbatProviderClient;
|
|
583
839
|
exports.Track = Track;
|
|
584
840
|
exports.createExperimentContext = createExperimentContext;
|
|
585
841
|
exports.fetchDecision = fetchDecision;
|
|
842
|
+
exports.flushMetrics = flushMetrics;
|
|
586
843
|
exports.sendMetric = sendMetric;
|
|
587
844
|
exports.useExperiment = useExperiment;
|
|
588
845
|
exports.useProbatMetrics = useProbatMetrics;
|