@cascayd/experiment 0.3.22 → 0.3.24
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/react/Experiment.js
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { getVariantStatus } from "../http";
|
|
4
|
+
import { record } from "../client";
|
|
5
|
+
import { getOrCreateSession, persistVariantChoice, readVariantChoice } from "../cookies";
|
|
6
|
+
import { buildWeights, chooseByWeightDeterministic, resolveVariant } from "../assignment";
|
|
7
7
|
export function Experiment({ id, children }) {
|
|
8
|
-
const [active, setActive] = useState(null);
|
|
9
|
-
const hasRunRef = useRef(false);
|
|
10
8
|
const childrenArray = useMemo(() => {
|
|
11
9
|
const arr = [];
|
|
12
10
|
for (const child of [].concat(children)) {
|
|
13
|
-
if (!child || child.type?.displayName !==
|
|
11
|
+
if (!child || child.type?.displayName !== "CascaydVariant")
|
|
14
12
|
continue;
|
|
15
13
|
arr.push({ id: child.props.id, weight: child.props.weight, element: child });
|
|
16
14
|
}
|
|
17
15
|
return arr;
|
|
18
16
|
}, [children]);
|
|
17
|
+
// Always render control initially (deterministic for SSR + hydration)
|
|
18
|
+
const initial = useMemo(() => resolveVariant("control", childrenArray), [childrenArray]);
|
|
19
|
+
const [active, setActive] = useState(initial);
|
|
20
|
+
const hasRunRef = useRef(false);
|
|
21
|
+
const sentImpressionsRef = useRef(new Set());
|
|
19
22
|
useEffect(() => {
|
|
20
23
|
if (hasRunRef.current)
|
|
21
24
|
return;
|
|
22
25
|
hasRunRef.current = true;
|
|
23
26
|
let cancelled = false;
|
|
24
27
|
const existing = readVariantChoice(id);
|
|
25
|
-
// Create or read a stable session id first
|
|
26
28
|
const sessionId = getOrCreateSession();
|
|
27
29
|
void (async () => {
|
|
28
30
|
try {
|
|
@@ -33,14 +35,14 @@ export function Experiment({ id, children }) {
|
|
|
33
35
|
return;
|
|
34
36
|
const serving = resolveVariant(status.serving_variant_id || existing, childrenArray);
|
|
35
37
|
applyAssignment(id, serving, setActive);
|
|
36
|
-
sendImpression(id, serving);
|
|
38
|
+
sendImpression(id, serving, sentImpressionsRef.current);
|
|
37
39
|
return;
|
|
38
40
|
}
|
|
39
41
|
catch {
|
|
40
|
-
// fall through
|
|
42
|
+
// fall through
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
|
-
const baseStatus = await getVariantStatus(id,
|
|
45
|
+
const baseStatus = await getVariantStatus(id, "control");
|
|
44
46
|
if (cancelled)
|
|
45
47
|
return;
|
|
46
48
|
const weighted = buildWeights(childrenArray, baseStatus.weights);
|
|
@@ -57,24 +59,21 @@ export function Experiment({ id, children }) {
|
|
|
57
59
|
}
|
|
58
60
|
const serving = resolveVariant(finalVariant, childrenArray);
|
|
59
61
|
applyAssignment(id, serving, setActive);
|
|
60
|
-
sendImpression(id, serving);
|
|
62
|
+
sendImpression(id, serving, sentImpressionsRef.current);
|
|
61
63
|
}
|
|
62
|
-
catch
|
|
63
|
-
// last-ditch fallback, deterministic equal weight using session id
|
|
64
|
+
catch {
|
|
64
65
|
const weighted = buildWeights(childrenArray, undefined);
|
|
65
66
|
const chosen = chooseByWeightDeterministic(weighted, id, sessionId);
|
|
66
67
|
if (cancelled)
|
|
67
68
|
return;
|
|
68
69
|
applyAssignment(id, chosen, setActive);
|
|
69
|
-
sendImpression(id, chosen);
|
|
70
|
+
sendImpression(id, chosen, sentImpressionsRef.current);
|
|
70
71
|
}
|
|
71
72
|
})();
|
|
72
73
|
return () => {
|
|
73
74
|
cancelled = true;
|
|
74
75
|
};
|
|
75
76
|
}, [id, childrenArray]);
|
|
76
|
-
if (!active)
|
|
77
|
-
return null;
|
|
78
77
|
const match = childrenArray.find((c) => c.id === active);
|
|
79
78
|
return match ? match.element : null;
|
|
80
79
|
}
|
|
@@ -82,11 +81,12 @@ function applyAssignment(experimentId, variantId, setActive) {
|
|
|
82
81
|
persistVariantChoice(experimentId, variantId);
|
|
83
82
|
setActive(variantId);
|
|
84
83
|
}
|
|
85
|
-
function sendImpression(experimentId, variantId) {
|
|
84
|
+
function sendImpression(experimentId, variantId, sent) {
|
|
86
85
|
if (!variantId)
|
|
87
86
|
return;
|
|
88
|
-
|
|
87
|
+
const key = `${experimentId}:${variantId}`;
|
|
88
|
+
if (sent.has(key))
|
|
89
89
|
return;
|
|
90
|
-
|
|
91
|
-
void record(
|
|
90
|
+
sent.add(key);
|
|
91
|
+
void record("impression", { experimentId, variantId });
|
|
92
92
|
}
|
package/dist/react/Variant.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export type VariantProps = {
|
|
|
3
3
|
weight?: number;
|
|
4
4
|
children?: React.ReactNode;
|
|
5
5
|
};
|
|
6
|
-
export declare function Variant({
|
|
6
|
+
export declare function Variant({ children }: VariantProps): import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export declare namespace Variant {
|
|
8
8
|
var displayName: string;
|
|
9
9
|
}
|
package/dist/react/Variant.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
"use client";
|
|
1
2
|
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
export function Variant({
|
|
3
|
+
export function Variant({ children }) {
|
|
3
4
|
return _jsx(_Fragment, { children: children });
|
|
4
5
|
}
|
|
5
|
-
Variant.displayName =
|
|
6
|
+
Variant.displayName = "CascaydVariant";
|