@probat/react 0.1.2 → 0.1.3
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 +1 -30
- package/dist/index.d.ts +1 -30
- package/dist/index.js +191 -193
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +193 -191
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -1
- package/src/context/ProbatContext.tsx +1 -12
- package/src/hoc/itrt-frontend.code-workspace +3 -0
- package/src/hoc/withExperiment.tsx +4 -12
- package/src/index.ts +0 -6
- package/src/utils/api.ts +158 -40
package/dist/index.d.mts
CHANGED
|
@@ -190,33 +190,4 @@ declare function writeChoice(proposalId: string, experiment_id: string, label: s
|
|
|
190
190
|
declare function hasTrackedVisit(proposalId: string, label: string): boolean;
|
|
191
191
|
declare function markTrackedVisit(proposalId: string, label: string): void;
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
* Document-level click tracking for Probat experiments
|
|
195
|
-
*
|
|
196
|
-
* This module implements event delegation at the document level to track clicks
|
|
197
|
-
* even when components don't have onClick handlers or when stopPropagation() is called.
|
|
198
|
-
*/
|
|
199
|
-
/**
|
|
200
|
-
* Initialize document-level click tracking
|
|
201
|
-
* Call this once when your app initializes (typically in ProbatProvider)
|
|
202
|
-
*/
|
|
203
|
-
declare function initDocumentClickTracking(): void;
|
|
204
|
-
/**
|
|
205
|
-
* Clean up the listener (useful for testing or cleanup)
|
|
206
|
-
*/
|
|
207
|
-
declare function cleanupDocumentClickTracking(): void;
|
|
208
|
-
/**
|
|
209
|
-
* Update proposal metadata cache (call this when proposal data changes)
|
|
210
|
-
* This is useful if you want to update the cache without waiting for DOM queries
|
|
211
|
-
*/
|
|
212
|
-
declare function updateProposalMetadata(proposalId: string, metadata: {
|
|
213
|
-
experimentId?: string | null;
|
|
214
|
-
variantLabel?: string;
|
|
215
|
-
apiBaseUrl?: string;
|
|
216
|
-
}): void;
|
|
217
|
-
/**
|
|
218
|
-
* Clear the proposal cache (useful for testing)
|
|
219
|
-
*/
|
|
220
|
-
declare function clearProposalCache(): void;
|
|
221
|
-
|
|
222
|
-
export { type Choice, type ProbatContextValue, ProbatProvider, ProbatProviderClient, type ProbatProviderProps as ProbatProviderClientProps, type ProbatProviderProps, type RetrieveResponse, type UseExperimentReturn, type UseProbatMetricsReturn, type WithExperimentOptions, cleanupDocumentClickTracking, clearProposalCache, detectEnvironment, extractClickMeta, fetchDecision, hasTrackedVisit, initDocumentClickTracking, markTrackedVisit, readChoice, sendMetric, updateProposalMetadata, useExperiment, useProbatContext, useProbatMetrics, withExperiment, writeChoice };
|
|
193
|
+
export { type Choice, type ProbatContextValue, ProbatProvider, ProbatProviderClient, type ProbatProviderProps as ProbatProviderClientProps, type ProbatProviderProps, type RetrieveResponse, type UseExperimentReturn, type UseProbatMetricsReturn, type WithExperimentOptions, detectEnvironment, extractClickMeta, fetchDecision, hasTrackedVisit, markTrackedVisit, readChoice, sendMetric, useExperiment, useProbatContext, useProbatMetrics, withExperiment, writeChoice };
|
package/dist/index.d.ts
CHANGED
|
@@ -190,33 +190,4 @@ declare function writeChoice(proposalId: string, experiment_id: string, label: s
|
|
|
190
190
|
declare function hasTrackedVisit(proposalId: string, label: string): boolean;
|
|
191
191
|
declare function markTrackedVisit(proposalId: string, label: string): void;
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
* Document-level click tracking for Probat experiments
|
|
195
|
-
*
|
|
196
|
-
* This module implements event delegation at the document level to track clicks
|
|
197
|
-
* even when components don't have onClick handlers or when stopPropagation() is called.
|
|
198
|
-
*/
|
|
199
|
-
/**
|
|
200
|
-
* Initialize document-level click tracking
|
|
201
|
-
* Call this once when your app initializes (typically in ProbatProvider)
|
|
202
|
-
*/
|
|
203
|
-
declare function initDocumentClickTracking(): void;
|
|
204
|
-
/**
|
|
205
|
-
* Clean up the listener (useful for testing or cleanup)
|
|
206
|
-
*/
|
|
207
|
-
declare function cleanupDocumentClickTracking(): void;
|
|
208
|
-
/**
|
|
209
|
-
* Update proposal metadata cache (call this when proposal data changes)
|
|
210
|
-
* This is useful if you want to update the cache without waiting for DOM queries
|
|
211
|
-
*/
|
|
212
|
-
declare function updateProposalMetadata(proposalId: string, metadata: {
|
|
213
|
-
experimentId?: string | null;
|
|
214
|
-
variantLabel?: string;
|
|
215
|
-
apiBaseUrl?: string;
|
|
216
|
-
}): void;
|
|
217
|
-
/**
|
|
218
|
-
* Clear the proposal cache (useful for testing)
|
|
219
|
-
*/
|
|
220
|
-
declare function clearProposalCache(): void;
|
|
221
|
-
|
|
222
|
-
export { type Choice, type ProbatContextValue, ProbatProvider, ProbatProviderClient, type ProbatProviderProps as ProbatProviderClientProps, type ProbatProviderProps, type RetrieveResponse, type UseExperimentReturn, type UseProbatMetricsReturn, type WithExperimentOptions, cleanupDocumentClickTracking, clearProposalCache, detectEnvironment, extractClickMeta, fetchDecision, hasTrackedVisit, initDocumentClickTracking, markTrackedVisit, readChoice, sendMetric, updateProposalMetadata, useExperiment, useProbatContext, useProbatMetrics, withExperiment, writeChoice };
|
|
193
|
+
export { type Choice, type ProbatContextValue, ProbatProvider, ProbatProviderClient, type ProbatProviderProps as ProbatProviderClientProps, type ProbatProviderProps, type RetrieveResponse, type UseExperimentReturn, type UseProbatMetricsReturn, type WithExperimentOptions, detectEnvironment, extractClickMeta, fetchDecision, hasTrackedVisit, markTrackedVisit, readChoice, sendMetric, useExperiment, useProbatContext, useProbatMetrics, withExperiment, writeChoice };
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,41 @@ function detectEnvironment() {
|
|
|
20
20
|
}
|
|
21
21
|
return "prod";
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
// src/context/ProbatContext.tsx
|
|
25
|
+
var ProbatContext = React4.createContext(null);
|
|
26
|
+
function ProbatProvider({
|
|
27
|
+
apiBaseUrl,
|
|
28
|
+
clientKey,
|
|
29
|
+
environment: explicitEnvironment,
|
|
30
|
+
repoFullName: explicitRepoFullName,
|
|
31
|
+
children
|
|
32
|
+
}) {
|
|
33
|
+
const contextValue = React4.useMemo(() => {
|
|
34
|
+
const resolvedApiBaseUrl = apiBaseUrl || typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)) }) !== "undefined" && undefined?.VITE_PROBAT_API || typeof globalThis !== "undefined" && globalThis.process?.env?.NEXT_PUBLIC_PROBAT_API || typeof window !== "undefined" && window.__PROBAT_API || "https://gushi.onrender.com";
|
|
35
|
+
const environment = explicitEnvironment || detectEnvironment();
|
|
36
|
+
const resolvedRepoFullName = explicitRepoFullName || typeof globalThis !== "undefined" && globalThis.process?.env?.NEXT_PUBLIC_PROBAT_REPO || typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)) }) !== "undefined" && undefined?.VITE_PROBAT_REPO || typeof window !== "undefined" && window.__PROBAT_REPO || void 0;
|
|
37
|
+
return {
|
|
38
|
+
apiBaseUrl: resolvedApiBaseUrl,
|
|
39
|
+
environment,
|
|
40
|
+
clientKey,
|
|
41
|
+
repoFullName: resolvedRepoFullName
|
|
42
|
+
};
|
|
43
|
+
}, [apiBaseUrl, clientKey, explicitEnvironment, explicitRepoFullName]);
|
|
44
|
+
return /* @__PURE__ */ React4__default.default.createElement(ProbatContext.Provider, { value: contextValue }, children);
|
|
45
|
+
}
|
|
46
|
+
function useProbatContext() {
|
|
47
|
+
const context = React4.useContext(ProbatContext);
|
|
48
|
+
if (!context) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"useProbatContext must be used within a ProbatProvider. Please wrap your app with <ProbatProvider>."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
return context;
|
|
54
|
+
}
|
|
55
|
+
function ProbatProviderClient(props) {
|
|
56
|
+
return React4__default.default.createElement(ProbatProvider, props);
|
|
57
|
+
}
|
|
23
58
|
var pendingFetches = /* @__PURE__ */ new Map();
|
|
24
59
|
async function fetchDecision(baseUrl, proposalId) {
|
|
25
60
|
const existingFetch = pendingFetches.get(proposalId);
|
|
@@ -62,7 +97,7 @@ async function sendMetric(baseUrl, proposalId, metricName, variantLabel = "contr
|
|
|
62
97
|
captured_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
63
98
|
};
|
|
64
99
|
try {
|
|
65
|
-
|
|
100
|
+
await fetch(url, {
|
|
66
101
|
method: "POST",
|
|
67
102
|
headers: {
|
|
68
103
|
Accept: "application/json",
|
|
@@ -72,26 +107,7 @@ async function sendMetric(baseUrl, proposalId, metricName, variantLabel = "contr
|
|
|
72
107
|
// CRITICAL: Include cookies to distinguish different users
|
|
73
108
|
body: JSON.stringify(body)
|
|
74
109
|
});
|
|
75
|
-
|
|
76
|
-
console.warn("[PROBAT] Metric send failed:", {
|
|
77
|
-
status: response.status,
|
|
78
|
-
statusText: response.statusText,
|
|
79
|
-
url,
|
|
80
|
-
body
|
|
81
|
-
});
|
|
82
|
-
} else {
|
|
83
|
-
console.log("[PROBAT] Metric sent successfully:", {
|
|
84
|
-
metricName,
|
|
85
|
-
proposalId,
|
|
86
|
-
variantLabel
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
} catch (error) {
|
|
90
|
-
console.error("[PROBAT] Error sending metric:", {
|
|
91
|
-
error: error instanceof Error ? error.message : String(error),
|
|
92
|
-
url,
|
|
93
|
-
body
|
|
94
|
-
});
|
|
110
|
+
} catch {
|
|
95
111
|
}
|
|
96
112
|
}
|
|
97
113
|
function extractClickMeta(event) {
|
|
@@ -152,7 +168,7 @@ if (typeof window !== "undefined") {
|
|
|
152
168
|
window.__probatReact = React4__default.default;
|
|
153
169
|
window.React = window.React || React4__default.default;
|
|
154
170
|
}
|
|
155
|
-
async function loadVariantComponent(baseUrl, proposalId, experimentId, filePath) {
|
|
171
|
+
async function loadVariantComponent(baseUrl, proposalId, experimentId, filePath, repoFullName, baseRef) {
|
|
156
172
|
if (!filePath) {
|
|
157
173
|
return null;
|
|
158
174
|
}
|
|
@@ -163,16 +179,151 @@ async function loadVariantComponent(baseUrl, proposalId, experimentId, filePath)
|
|
|
163
179
|
}
|
|
164
180
|
const loadPromise = (async () => {
|
|
165
181
|
try {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
182
|
+
try {
|
|
183
|
+
const variantUrl = `/probat/${filePath}`;
|
|
184
|
+
const mod = await import(
|
|
185
|
+
/* @vite-ignore */
|
|
186
|
+
variantUrl
|
|
187
|
+
);
|
|
188
|
+
const VariantComponent2 = mod?.default || mod;
|
|
189
|
+
if (VariantComponent2 && typeof VariantComponent2 === "function") {
|
|
190
|
+
return VariantComponent2;
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
194
|
+
let code = "";
|
|
195
|
+
let rawCode = "";
|
|
196
|
+
let rawCodeFetched = false;
|
|
197
|
+
const localUrl = `/probat/${filePath}`;
|
|
198
|
+
try {
|
|
199
|
+
const localRes = await fetch(localUrl, {
|
|
200
|
+
method: "GET",
|
|
201
|
+
headers: { Accept: "text/plain" }
|
|
202
|
+
});
|
|
203
|
+
if (localRes.ok) {
|
|
204
|
+
rawCode = await localRes.text();
|
|
205
|
+
rawCodeFetched = true;
|
|
206
|
+
console.log(`[PROBAT] \u2705 Loaded variant from local (user's repo): ${localUrl}`);
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
console.debug(`[PROBAT] Local file not available (${localUrl}), trying GitHub...`);
|
|
210
|
+
}
|
|
211
|
+
if (!rawCodeFetched && repoFullName) {
|
|
212
|
+
const githubPath = `probat/${filePath}`;
|
|
213
|
+
const gitRef = baseRef || "main";
|
|
214
|
+
const githubUrl = `https://raw.githubusercontent.com/${repoFullName}/${gitRef}/${githubPath}`;
|
|
215
|
+
const res = await fetch(githubUrl, { method: "GET", headers: { Accept: "text/plain" } });
|
|
216
|
+
if (res.ok) {
|
|
217
|
+
rawCode = await res.text();
|
|
218
|
+
rawCodeFetched = true;
|
|
219
|
+
console.log(`[PROBAT] \u26A0\uFE0F Loaded variant from GitHub (fallback): ${githubUrl}`);
|
|
220
|
+
} else {
|
|
221
|
+
console.warn(`[PROBAT] \u26A0\uFE0F GitHub fetch failed (${res.status}), falling back to server compilation`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (rawCodeFetched && rawCode) {
|
|
225
|
+
let Babel;
|
|
226
|
+
if (typeof window !== "undefined" && window.Babel) {
|
|
227
|
+
Babel = window.Babel;
|
|
228
|
+
} else {
|
|
229
|
+
try {
|
|
230
|
+
const babelModule = await import('@babel/standalone');
|
|
231
|
+
Babel = babelModule.default || babelModule;
|
|
232
|
+
} catch (importError) {
|
|
233
|
+
try {
|
|
234
|
+
await new Promise((resolve, reject) => {
|
|
235
|
+
if (typeof document === "undefined") {
|
|
236
|
+
reject(new Error("Document not available"));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (window.Babel) {
|
|
240
|
+
Babel = window.Babel;
|
|
241
|
+
resolve();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const script = document.createElement("script");
|
|
245
|
+
script.src = "https://unpkg.com/@babel/standalone/babel.min.js";
|
|
246
|
+
script.async = true;
|
|
247
|
+
script.onload = () => {
|
|
248
|
+
Babel = window.Babel;
|
|
249
|
+
if (!Babel) reject(new Error("Babel not found after script load"));
|
|
250
|
+
else resolve();
|
|
251
|
+
};
|
|
252
|
+
script.onerror = () => reject(new Error("Failed to load Babel from CDN"));
|
|
253
|
+
document.head.appendChild(script);
|
|
254
|
+
});
|
|
255
|
+
} catch (babelError) {
|
|
256
|
+
console.error("[PROBAT] Failed to load Babel, falling back to server compilation", babelError);
|
|
257
|
+
rawCodeFetched = false;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (rawCodeFetched && rawCode && Babel) {
|
|
262
|
+
const isTSX = filePath.endsWith(".tsx");
|
|
263
|
+
rawCode = rawCode.replace(/^import\s+['"].*\.css['"];?\s*$/gm, "");
|
|
264
|
+
rawCode = rawCode.replace(/^import\s+.*from\s+['"].*\.css['"];?\s*$/gm, "");
|
|
265
|
+
rawCode = rawCode.replace(
|
|
266
|
+
/^import\s+React(?:\s*,\s*\{[^}]*\})?\s+from\s+['"]react['"];?\s*$/m,
|
|
267
|
+
"const React = window.React || globalThis.React;"
|
|
268
|
+
);
|
|
269
|
+
rawCode = rawCode.replace(
|
|
270
|
+
/^import\s+\*\s+as\s+React\s+from\s+['"]react['"];?\s*$/m,
|
|
271
|
+
"const React = window.React || globalThis.React;"
|
|
272
|
+
);
|
|
273
|
+
rawCode = rawCode.replace(
|
|
274
|
+
/^import\s+\{([^}]+)\}\s+from\s+['"]react['"];?\s*$/m,
|
|
275
|
+
(match, imports) => `const {${imports}} = window.React || globalThis.React;`
|
|
276
|
+
);
|
|
277
|
+
rawCode = rawCode.replace(/import\.meta\.env\.[\w$]+/g, "undefined");
|
|
278
|
+
rawCode = rawCode.replace(/\bimport\.meta\b/g, "({})");
|
|
279
|
+
rawCode = rawCode.replace(/^import\s+.*\/@vite\/client.*$/gm, "");
|
|
280
|
+
rawCode = rawCode.replace(/import\.meta\.hot(?:\.[\w$]+)*/g, "undefined");
|
|
281
|
+
const compiled = Babel.transform(rawCode, {
|
|
282
|
+
presets: [
|
|
283
|
+
["react", { runtime: "classic" }],
|
|
284
|
+
["typescript", { allExtensions: true, isTSX }]
|
|
285
|
+
],
|
|
286
|
+
plugins: [["transform-modules-commonjs", { allowTopLevelThis: true }]],
|
|
287
|
+
sourceType: "module",
|
|
288
|
+
filename: filePath
|
|
289
|
+
}).code;
|
|
290
|
+
code = `
|
|
291
|
+
var __probatVariant = (function() {
|
|
292
|
+
var require = function(name) {
|
|
293
|
+
if (name === "react" || name === "react/jsx-runtime") {
|
|
294
|
+
return window.React || globalThis.React;
|
|
295
|
+
}
|
|
296
|
+
if (name.startsWith("/@vite") || name.includes("@vite/client") || name.includes(".vite/deps")) {
|
|
297
|
+
return {};
|
|
298
|
+
}
|
|
299
|
+
if (name === "react/jsx-runtime.js") {
|
|
300
|
+
return window.React || globalThis.React;
|
|
301
|
+
}
|
|
302
|
+
throw new Error("Unsupported module: " + name);
|
|
303
|
+
};
|
|
304
|
+
var module = { exports: {} };
|
|
305
|
+
var exports = module.exports;
|
|
306
|
+
${compiled}
|
|
307
|
+
return module.exports.default || module.exports;
|
|
308
|
+
})();
|
|
309
|
+
`;
|
|
310
|
+
} else {
|
|
311
|
+
rawCodeFetched = false;
|
|
312
|
+
code = "";
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (!rawCodeFetched || code === "") {
|
|
316
|
+
const variantUrl = `${baseUrl.replace(/\/$/, "")}/variants/${filePath}`;
|
|
317
|
+
const serverRes = await fetch(variantUrl, {
|
|
318
|
+
method: "GET",
|
|
319
|
+
headers: { Accept: "text/javascript" },
|
|
320
|
+
credentials: "include"
|
|
321
|
+
});
|
|
322
|
+
if (!serverRes.ok) {
|
|
323
|
+
throw new Error(`HTTP ${serverRes.status}`);
|
|
324
|
+
}
|
|
325
|
+
code = await serverRes.text();
|
|
174
326
|
}
|
|
175
|
-
const code = await res.text();
|
|
176
327
|
if (typeof window !== "undefined") {
|
|
177
328
|
window.React = window.React || React4__default.default;
|
|
178
329
|
}
|
|
@@ -199,146 +350,7 @@ async function loadVariantComponent(baseUrl, proposalId, experimentId, filePath)
|
|
|
199
350
|
return loadPromise;
|
|
200
351
|
}
|
|
201
352
|
|
|
202
|
-
// src/
|
|
203
|
-
var proposalCache = /* @__PURE__ */ new Map();
|
|
204
|
-
var isListenerAttached = false;
|
|
205
|
-
var lastClickTime = /* @__PURE__ */ new Map();
|
|
206
|
-
var DEBOUNCE_MS = 100;
|
|
207
|
-
function getProposalMetadata(element) {
|
|
208
|
-
const probatWrapper = element.closest("[data-probat-proposal]");
|
|
209
|
-
if (!probatWrapper) return null;
|
|
210
|
-
const proposalId = probatWrapper.getAttribute("data-probat-proposal");
|
|
211
|
-
if (!proposalId) return null;
|
|
212
|
-
const cacheKey = `${proposalId}`;
|
|
213
|
-
const cached = proposalCache.get(cacheKey);
|
|
214
|
-
if (cached) return cached;
|
|
215
|
-
const experimentId = probatWrapper.getAttribute("data-probat-experiment-id");
|
|
216
|
-
const variantLabel = probatWrapper.getAttribute("data-probat-variant-label") || "control";
|
|
217
|
-
const apiBaseUrl = probatWrapper.getAttribute("data-probat-api-base-url") || typeof window !== "undefined" && window.__PROBAT_API || "https://gushi.onrender.com";
|
|
218
|
-
const metadata = {
|
|
219
|
-
proposalId,
|
|
220
|
-
experimentId: experimentId || null,
|
|
221
|
-
variantLabel,
|
|
222
|
-
apiBaseUrl
|
|
223
|
-
};
|
|
224
|
-
proposalCache.set(cacheKey, metadata);
|
|
225
|
-
return metadata;
|
|
226
|
-
}
|
|
227
|
-
function handleDocumentClick(event) {
|
|
228
|
-
const target = event.target;
|
|
229
|
-
if (!target) return;
|
|
230
|
-
const metadata = getProposalMetadata(target);
|
|
231
|
-
if (!metadata) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
const now2 = Date.now();
|
|
235
|
-
const lastClick = lastClickTime.get(metadata.proposalId) || 0;
|
|
236
|
-
if (now2 - lastClick < DEBOUNCE_MS) {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
lastClickTime.set(metadata.proposalId, now2);
|
|
240
|
-
const clickMeta = extractClickMeta(event);
|
|
241
|
-
target.hasAttribute("data-probat-track") || target.closest("[data-probat-track]") !== null;
|
|
242
|
-
const finalMeta = clickMeta || {
|
|
243
|
-
target_tag: target.tagName,
|
|
244
|
-
target_class: target.className || "",
|
|
245
|
-
target_id: target.id || "",
|
|
246
|
-
clicked_inside_probat: true
|
|
247
|
-
// Flag to indicate this was tracked via document-level listener
|
|
248
|
-
};
|
|
249
|
-
const experimentId = metadata.variantLabel === "control" ? void 0 : metadata.experimentId && !metadata.experimentId.startsWith("exp_") ? metadata.experimentId : void 0;
|
|
250
|
-
void sendMetric(
|
|
251
|
-
metadata.apiBaseUrl,
|
|
252
|
-
metadata.proposalId,
|
|
253
|
-
"click",
|
|
254
|
-
metadata.variantLabel,
|
|
255
|
-
experimentId,
|
|
256
|
-
finalMeta
|
|
257
|
-
);
|
|
258
|
-
console.log("[PROBAT] Click tracked:", {
|
|
259
|
-
proposalId: metadata.proposalId,
|
|
260
|
-
variantLabel: metadata.variantLabel,
|
|
261
|
-
target: target.tagName,
|
|
262
|
-
targetId: target.id || "none",
|
|
263
|
-
targetClass: target.className || "none",
|
|
264
|
-
meta: finalMeta
|
|
265
|
-
});
|
|
266
|
-
console.log("[PROBAT] Sending metric to:", `${metadata.apiBaseUrl}/send_metrics/${metadata.proposalId}`);
|
|
267
|
-
}
|
|
268
|
-
function initDocumentClickTracking() {
|
|
269
|
-
if (isListenerAttached) {
|
|
270
|
-
console.warn("[PROBAT] Document click listener already attached");
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
if (typeof document === "undefined") {
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
document.addEventListener("click", handleDocumentClick, true);
|
|
277
|
-
isListenerAttached = true;
|
|
278
|
-
console.log("[PROBAT] Document-level click tracking initialized");
|
|
279
|
-
}
|
|
280
|
-
function cleanupDocumentClickTracking() {
|
|
281
|
-
if (!isListenerAttached) return;
|
|
282
|
-
if (typeof document !== "undefined") {
|
|
283
|
-
document.removeEventListener("click", handleDocumentClick, true);
|
|
284
|
-
}
|
|
285
|
-
isListenerAttached = false;
|
|
286
|
-
proposalCache.clear();
|
|
287
|
-
lastClickTime.clear();
|
|
288
|
-
}
|
|
289
|
-
function updateProposalMetadata(proposalId, metadata) {
|
|
290
|
-
const existing = proposalCache.get(proposalId);
|
|
291
|
-
if (existing) {
|
|
292
|
-
proposalCache.set(proposalId, {
|
|
293
|
-
...existing,
|
|
294
|
-
...metadata
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function clearProposalCache() {
|
|
299
|
-
proposalCache.clear();
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// src/context/ProbatContext.tsx
|
|
303
|
-
var ProbatContext = React4.createContext(null);
|
|
304
|
-
function ProbatProvider({
|
|
305
|
-
apiBaseUrl,
|
|
306
|
-
clientKey,
|
|
307
|
-
environment: explicitEnvironment,
|
|
308
|
-
repoFullName: explicitRepoFullName,
|
|
309
|
-
children
|
|
310
|
-
}) {
|
|
311
|
-
const contextValue = React4.useMemo(() => {
|
|
312
|
-
const resolvedApiBaseUrl = apiBaseUrl || typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)) }) !== "undefined" && undefined?.VITE_PROBAT_API || typeof globalThis !== "undefined" && globalThis.process?.env?.NEXT_PUBLIC_PROBAT_API || typeof window !== "undefined" && window.__PROBAT_API || "https://gushi.onrender.com";
|
|
313
|
-
const environment = explicitEnvironment || detectEnvironment();
|
|
314
|
-
const resolvedRepoFullName = explicitRepoFullName || typeof globalThis !== "undefined" && globalThis.process?.env?.NEXT_PUBLIC_PROBAT_REPO || typeof ({ url: (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)) }) !== "undefined" && undefined?.VITE_PROBAT_REPO || typeof window !== "undefined" && window.__PROBAT_REPO || void 0;
|
|
315
|
-
return {
|
|
316
|
-
apiBaseUrl: resolvedApiBaseUrl,
|
|
317
|
-
environment,
|
|
318
|
-
clientKey,
|
|
319
|
-
repoFullName: resolvedRepoFullName
|
|
320
|
-
};
|
|
321
|
-
}, [apiBaseUrl, clientKey, explicitEnvironment, explicitRepoFullName]);
|
|
322
|
-
React4.useEffect(() => {
|
|
323
|
-
initDocumentClickTracking();
|
|
324
|
-
return () => {
|
|
325
|
-
cleanupDocumentClickTracking();
|
|
326
|
-
};
|
|
327
|
-
}, []);
|
|
328
|
-
return /* @__PURE__ */ React4__default.default.createElement(ProbatContext.Provider, { value: contextValue }, children);
|
|
329
|
-
}
|
|
330
|
-
function useProbatContext() {
|
|
331
|
-
const context = React4.useContext(ProbatContext);
|
|
332
|
-
if (!context) {
|
|
333
|
-
throw new Error(
|
|
334
|
-
"useProbatContext must be used within a ProbatProvider. Please wrap your app with <ProbatProvider>."
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
return context;
|
|
338
|
-
}
|
|
339
|
-
function ProbatProviderClient(props) {
|
|
340
|
-
return React4__default.default.createElement(ProbatProvider, props);
|
|
341
|
-
}
|
|
353
|
+
// src/hooks/useProbatMetrics.ts
|
|
342
354
|
function useProbatMetrics() {
|
|
343
355
|
const { apiBaseUrl } = useProbatContext();
|
|
344
356
|
const trackClick = React4.useCallback(
|
|
@@ -602,7 +614,9 @@ function withExperiment(Control, options) {
|
|
|
602
614
|
apiBaseUrl,
|
|
603
615
|
componentConfig.proposal_id,
|
|
604
616
|
variantInfo.experiment_id,
|
|
605
|
-
variantInfo.file_path
|
|
617
|
+
variantInfo.file_path,
|
|
618
|
+
componentConfig.repo_full_name,
|
|
619
|
+
componentConfig.base_ref
|
|
606
620
|
);
|
|
607
621
|
if (VariantComp && typeof VariantComp === "function" && alive) {
|
|
608
622
|
variantComponents[label2] = VariantComp;
|
|
@@ -701,23 +715,11 @@ function withExperiment(Control, options) {
|
|
|
701
715
|
}
|
|
702
716
|
const label = choice?.label ?? "control";
|
|
703
717
|
const Variant = registry[label] || registry.control || ControlComponent;
|
|
704
|
-
return /* @__PURE__ */ React4__default.default.createElement(
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
},
|
|
710
|
-
"data-probat-proposal": proposalId,
|
|
711
|
-
"data-probat-experiment-id": choice?.experiment_id || "",
|
|
712
|
-
"data-probat-variant-label": label,
|
|
713
|
-
"data-probat-api-base-url": apiBaseUrl
|
|
714
|
-
},
|
|
715
|
-
React4__default.default.createElement(Variant, {
|
|
716
|
-
key: `${proposalId}:${label}`,
|
|
717
|
-
...props,
|
|
718
|
-
probat: { trackClick: () => trackClick(null, { force: true }) }
|
|
719
|
-
})
|
|
720
|
-
);
|
|
718
|
+
return /* @__PURE__ */ React4__default.default.createElement("div", { onClick: (event) => trackClick(event), "data-probat-proposal": proposalId }, React4__default.default.createElement(Variant, {
|
|
719
|
+
key: `${proposalId}:${label}`,
|
|
720
|
+
...props,
|
|
721
|
+
probat: { trackClick: () => trackClick(null, { force: true }) }
|
|
722
|
+
}));
|
|
721
723
|
}
|
|
722
724
|
Wrapped.displayName = `withExperiment(${Control.displayName || Control.name || "Component"})`;
|
|
723
725
|
return Wrapped;
|
|
@@ -725,17 +727,13 @@ function withExperiment(Control, options) {
|
|
|
725
727
|
|
|
726
728
|
exports.ProbatProvider = ProbatProvider;
|
|
727
729
|
exports.ProbatProviderClient = ProbatProviderClient;
|
|
728
|
-
exports.cleanupDocumentClickTracking = cleanupDocumentClickTracking;
|
|
729
|
-
exports.clearProposalCache = clearProposalCache;
|
|
730
730
|
exports.detectEnvironment = detectEnvironment;
|
|
731
731
|
exports.extractClickMeta = extractClickMeta;
|
|
732
732
|
exports.fetchDecision = fetchDecision;
|
|
733
733
|
exports.hasTrackedVisit = hasTrackedVisit;
|
|
734
|
-
exports.initDocumentClickTracking = initDocumentClickTracking;
|
|
735
734
|
exports.markTrackedVisit = markTrackedVisit;
|
|
736
735
|
exports.readChoice = readChoice;
|
|
737
736
|
exports.sendMetric = sendMetric;
|
|
738
|
-
exports.updateProposalMetadata = updateProposalMetadata;
|
|
739
737
|
exports.useExperiment = useExperiment;
|
|
740
738
|
exports.useProbatContext = useProbatContext;
|
|
741
739
|
exports.useProbatMetrics = useProbatMetrics;
|