@cascayd/experiment 0.3.11 → 0.3.12
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/client.js +11 -1
- package/dist/shopify-handler-browser.d.ts +1 -0
- package/dist/shopify-handler-browser.js +144 -414
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -168,11 +168,21 @@ export async function record(type, opts = {}) {
|
|
|
168
168
|
body.experiment_id = opts.experimentId;
|
|
169
169
|
const fromCookie = readVariantChoice(opts.experimentId);
|
|
170
170
|
const variantId = opts.variantId || fromCookie;
|
|
171
|
-
if (variantId)
|
|
171
|
+
if (variantId) {
|
|
172
172
|
body.variant_id = variantId;
|
|
173
|
+
}
|
|
174
|
+
else if (opts.experimentId) {
|
|
175
|
+
console.warn(`[cascayd-sdk] No variant_id found for experiment ${opts.experimentId}`);
|
|
176
|
+
}
|
|
173
177
|
}
|
|
174
178
|
if (typeof opts.value === 'number')
|
|
175
179
|
body.value = opts.value;
|
|
180
|
+
if (opts.route) {
|
|
181
|
+
body.route = opts.route;
|
|
182
|
+
}
|
|
183
|
+
else if (typeof window !== 'undefined' && window.location) {
|
|
184
|
+
body.route = window.location.pathname;
|
|
185
|
+
}
|
|
176
186
|
const res = await fetch(`${BASE_URL}/events`, {
|
|
177
187
|
method: 'POST',
|
|
178
188
|
headers: {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,433 +1,163 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* <script type="module" src="https://cdn.jsdelivr.net/npm/@cascayd/experiment@latest/dist/client.js"></script>
|
|
9
|
-
* 2. Load this handler:
|
|
10
|
-
* <script src="https://cdn.jsdelivr.net/npm/@cascayd/experiment@latest/dist/shopify-handler-browser.js"></script>
|
|
11
|
-
*
|
|
12
|
-
* Or if SDK is loaded as a module that exposes functions to window:
|
|
13
|
-
* <script>
|
|
14
|
-
* window.initCascayd({ apiKey: 'YOUR_KEY' });
|
|
15
|
-
* </script>
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
(function() {
|
|
19
|
-
'use strict';
|
|
20
|
-
|
|
21
|
-
console.log('[cascayd-shopify] 🚀 Handler script starting...');
|
|
22
|
-
console.log('[cascayd-shopify] Document readyState:', document.readyState);
|
|
23
|
-
console.log('[cascayd-shopify] Window object available:', typeof window !== 'undefined');
|
|
24
|
-
|
|
25
|
-
// Cookie helper functions (matching SDK's cookie logic)
|
|
26
|
-
function getCookie(name) {
|
|
27
|
-
const match = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/[.$?*|{}()\[\]\\\/\+^]/g, '\\$&') + '=([^;]*)'));
|
|
28
|
-
const value = match ? decodeURIComponent(match[1]) : null;
|
|
29
|
-
console.log('[cascayd-shopify] 📦 getCookie:', name, '=', value);
|
|
30
|
-
return value;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function setCookie(name, value) {
|
|
34
|
-
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; SameSite=Lax; max-age=31536000`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function getVariantCookieKey(experimentId) {
|
|
38
|
-
return `cascayd:${experimentId}`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function readVariantChoice(experimentId) {
|
|
42
|
-
const choice = getCookie(getVariantCookieKey(experimentId));
|
|
43
|
-
console.log('[cascayd-shopify] 📖 Read variant choice:', { experimentId, choice });
|
|
44
|
-
return choice;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function persistVariantChoice(experimentId, variantId) {
|
|
48
|
-
const key = getVariantCookieKey(experimentId);
|
|
49
|
-
console.log('[cascayd-shopify] 💾 Persisting variant choice:', { experimentId, variantId, cookieKey: key });
|
|
50
|
-
setCookie(key, variantId);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// API configuration (same as SDK)
|
|
54
|
-
const BASE_URL = 'https://ab-mvp-backend.onrender.com';
|
|
55
|
-
let API_KEY = '';
|
|
56
|
-
|
|
57
|
-
// Try to get API key from window (if SDK was initialized)
|
|
58
|
-
function getAPIKey() {
|
|
59
|
-
if (typeof window !== 'undefined' && window._cascaydAPIKey) {
|
|
60
|
-
return window._cascaydAPIKey;
|
|
61
|
-
}
|
|
62
|
-
return API_KEY;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Direct API call functions (same logic as SDK)
|
|
66
|
-
async function getVariantStatus(experimentId, variantId) {
|
|
67
|
-
const url = BASE_URL + '/experiments/' + encodeURIComponent(experimentId) + '/variants/' + encodeURIComponent(variantId) + '/status';
|
|
68
|
-
console.log('[cascayd-shopify] 🌐 Fetching variant status:', { experimentId, variantId, url });
|
|
69
|
-
try {
|
|
70
|
-
const res = await fetch(url);
|
|
71
|
-
const data = await res.json();
|
|
72
|
-
console.log('[cascayd-shopify] ✅ Variant status response:', { status: res.status, data });
|
|
73
|
-
if (!res.ok) {
|
|
74
|
-
const err = new Error('Variant status failed: ' + res.status);
|
|
75
|
-
err.payload = data;
|
|
76
|
-
throw err;
|
|
77
|
-
}
|
|
78
|
-
return data;
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error('[cascayd-shopify] ❌ getVariantStatus error:', error);
|
|
81
|
-
throw error;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function chooseByWeight(variants) {
|
|
86
|
-
const total = variants.reduce(function(s, v) { return s + v.weight; }, 0);
|
|
1
|
+
import { record, getVariantStatus } from './client';
|
|
2
|
+
import { getOrCreateSession, persistVariantChoice, readVariantChoice } from './cookies';
|
|
3
|
+
const sentImpressions = new Set();
|
|
4
|
+
function chooseByWeight(variants) {
|
|
5
|
+
const total = variants.reduce((s, v) => s + v.weight, 0);
|
|
6
|
+
if (total === 0)
|
|
7
|
+
return variants[0]?.id ?? 'control';
|
|
87
8
|
const r = Math.random() * total;
|
|
88
9
|
let acc = 0;
|
|
89
|
-
for (
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
10
|
+
for (const v of variants) {
|
|
11
|
+
acc += v.weight;
|
|
12
|
+
if (r <= acc)
|
|
13
|
+
return v.id;
|
|
94
14
|
}
|
|
95
|
-
return variants[variants.length - 1]
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
function baseStatusToVariants(status) {
|
|
15
|
+
return variants[variants.length - 1]?.id ?? 'control';
|
|
16
|
+
}
|
|
17
|
+
function baseStatusToVariants(status) {
|
|
99
18
|
const entries = Object.entries(status.weights || {});
|
|
100
19
|
if (entries.length === 0) {
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
return entries.map(function(entry) {
|
|
104
|
-
return { id: entry[0], weight: entry[1] };
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Assign variant using same logic as SDK
|
|
109
|
-
async function assignVariantDirect(experimentId) {
|
|
110
|
-
console.log('[cascayd-shopify] 🎲 assignVariantDirect called for:', experimentId);
|
|
111
|
-
const baseStatus = await getVariantStatus(experimentId, 'control');
|
|
112
|
-
console.log('[cascayd-shopify] 📊 Base status received:', baseStatus);
|
|
113
|
-
const weights = baseStatus.weights || {};
|
|
114
|
-
const variants = Object.entries(weights).map(function(entry) {
|
|
115
|
-
return { id: entry[0], weight: entry[1] };
|
|
116
|
-
});
|
|
117
|
-
console.log('[cascayd-shopify] 📋 Variants from weights:', variants);
|
|
118
|
-
|
|
119
|
-
const allVariants = variants.length > 0 ? variants : baseStatusToVariants(baseStatus);
|
|
120
|
-
console.log('[cascayd-shopify] 🎯 All variants for selection:', allVariants);
|
|
121
|
-
let candidate = chooseByWeight(allVariants);
|
|
122
|
-
console.log('[cascayd-shopify] 🎲 Selected candidate:', candidate);
|
|
123
|
-
let finalStatus = null;
|
|
124
|
-
|
|
125
|
-
if (candidate) {
|
|
126
|
-
try {
|
|
127
|
-
finalStatus = await getVariantStatus(experimentId, candidate);
|
|
128
|
-
console.log('[cascayd-shopify] ✅ Final status for candidate:', finalStatus);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error('[cascayd-shopify] ❌ getVariantStatus failed for candidate', { experimentId: experimentId, candidate: candidate, error: error });
|
|
131
|
-
// Fall back to candidate
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const serving = (finalStatus && finalStatus.serving_variant_id) || candidate || 'control';
|
|
136
|
-
console.log('[cascayd-shopify] ✅ Final serving variant:', serving);
|
|
137
|
-
return { variantId: serving };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Record event using same logic as SDK
|
|
141
|
-
async function recordDirect(type, opts) {
|
|
142
|
-
const sessionId = getOrCreateSession();
|
|
143
|
-
const body = {
|
|
144
|
-
type: type,
|
|
145
|
-
session_id: sessionId
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
if (opts.experimentId) {
|
|
149
|
-
body.experiment_id = opts.experimentId;
|
|
150
|
-
const fromCookie = readVariantChoice(opts.experimentId);
|
|
151
|
-
const variantId = opts.variantId || fromCookie;
|
|
152
|
-
if (variantId) {
|
|
153
|
-
body.variant_id = variantId;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (typeof opts.value === 'number') {
|
|
158
|
-
body.value = opts.value;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const headers = {
|
|
162
|
-
'Content-Type': 'application/json'
|
|
163
|
-
};
|
|
164
|
-
const apiKey = getAPIKey();
|
|
165
|
-
if (apiKey) {
|
|
166
|
-
headers.Authorization = 'Bearer ' + apiKey;
|
|
20
|
+
return [{ id: 'control', weight: 1 }];
|
|
167
21
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
id = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
185
|
-
const r = (crypto.getRandomValues(new Uint8Array(1))[0] & 0xf) >>> 0;
|
|
186
|
-
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
|
187
|
-
return v.toString(16);
|
|
188
|
-
});
|
|
189
|
-
setCookie(key, id);
|
|
190
|
-
return id;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Try to use SDK functions if available, otherwise use direct API calls
|
|
194
|
-
async function waitForSDK(maxAttempts, interval) {
|
|
195
|
-
maxAttempts = maxAttempts || 10;
|
|
196
|
-
interval = interval || 100;
|
|
197
|
-
console.log('[cascayd-shopify] ⏳ Waiting for SDK...', { maxAttempts, interval });
|
|
198
|
-
|
|
199
|
-
return new Promise(function(resolve) {
|
|
200
|
-
let attempts = 0;
|
|
201
|
-
const checkSDK = function() {
|
|
202
|
-
attempts++;
|
|
203
|
-
console.log('[cascayd-shopify] 🔍 Checking for SDK (attempt ' + attempts + '/' + maxAttempts + ')');
|
|
204
|
-
|
|
205
|
-
// Check if SDK functions are available on window
|
|
206
|
-
let assignVariantFn = null;
|
|
207
|
-
let recordFn = null;
|
|
208
|
-
|
|
209
|
-
if (typeof window !== 'undefined') {
|
|
210
|
-
console.log('[cascayd-shopify] 🪟 Window available, checking functions...');
|
|
211
|
-
console.log('[cascayd-shopify] - window.assignVariant:', typeof window.assignVariant);
|
|
212
|
-
console.log('[cascayd-shopify] - window.record:', typeof window.record);
|
|
213
|
-
console.log('[cascayd-shopify] - window.Cascayd:', typeof window.Cascayd);
|
|
214
|
-
console.log('[cascayd-shopify] - window.initCascayd:', typeof window.initCascayd);
|
|
215
|
-
|
|
216
|
-
if (window.assignVariant) {
|
|
217
|
-
assignVariantFn = window.assignVariant;
|
|
218
|
-
console.log('[cascayd-shopify] ✅ Found window.assignVariant');
|
|
219
|
-
}
|
|
220
|
-
if (window.record) {
|
|
221
|
-
recordFn = window.record;
|
|
222
|
-
console.log('[cascayd-shopify] ✅ Found window.record');
|
|
223
|
-
}
|
|
224
|
-
// Try Cascayd namespace
|
|
225
|
-
if (!assignVariantFn && window.Cascayd && window.Cascayd.assignVariant) {
|
|
226
|
-
assignVariantFn = window.Cascayd.assignVariant;
|
|
227
|
-
console.log('[cascayd-shopify] ✅ Found Cascayd.assignVariant');
|
|
228
|
-
}
|
|
229
|
-
if (!recordFn && window.Cascayd && window.Cascayd.record) {
|
|
230
|
-
recordFn = window.Cascayd.record;
|
|
231
|
-
console.log('[cascayd-shopify] ✅ Found Cascayd.record');
|
|
232
|
-
}
|
|
22
|
+
return entries.map(([id, weight]) => ({ id, weight }));
|
|
23
|
+
}
|
|
24
|
+
async function assignVariantForExperiment(experimentId) {
|
|
25
|
+
try {
|
|
26
|
+
const existing = readVariantChoice(experimentId);
|
|
27
|
+
if (existing) {
|
|
28
|
+
try {
|
|
29
|
+
const status = await getVariantStatus(experimentId, existing);
|
|
30
|
+
const serving = status.serving_variant_id || existing;
|
|
31
|
+
if (serving === existing) {
|
|
32
|
+
return existing;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.warn('[cascayd-shopify] Failed to verify existing variant, reassigning', error);
|
|
37
|
+
}
|
|
233
38
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
39
|
+
const baseStatus = await getVariantStatus(experimentId, 'control');
|
|
40
|
+
const weights = baseStatus.weights || {};
|
|
41
|
+
const variants = Object.entries(weights).map(([id, weight]) => ({ id, weight }));
|
|
42
|
+
let candidate = chooseByWeight(variants.length > 0 ? variants : baseStatusToVariants(baseStatus));
|
|
43
|
+
let finalStatus = null;
|
|
44
|
+
if (candidate) {
|
|
45
|
+
try {
|
|
46
|
+
finalStatus = await getVariantStatus(experimentId, candidate);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.warn('[cascayd-shopify] getVariantStatus failed, using candidate', error);
|
|
50
|
+
}
|
|
245
51
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Record impression event
|
|
252
|
-
async function recordImpression(experimentId, variantId, sdk) {
|
|
253
|
-
console.log('[cascayd-shopify] 📝 Recording impression:', { experimentId, variantId, hasSDK: !!sdk, hasRecordFn: !!(sdk && sdk.record) });
|
|
254
|
-
if (!sdk || !sdk.record) {
|
|
255
|
-
console.warn('[cascayd-shopify] ⚠️ Record function not available, skipping impression tracking');
|
|
256
|
-
return;
|
|
52
|
+
const serving = finalStatus?.serving_variant_id || candidate || 'control';
|
|
53
|
+
persistVariantChoice(experimentId, serving);
|
|
54
|
+
return serving;
|
|
257
55
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
experimentId: experimentId,
|
|
262
|
-
variantId: variantId
|
|
263
|
-
});
|
|
264
|
-
console.log('[cascayd-shopify] ✅ Impression recorded successfully');
|
|
265
|
-
} catch (error) {
|
|
266
|
-
console.error('[cascayd-shopify] ❌ Failed to record impression:', error);
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error('[cascayd-shopify] Variant assignment failed', error);
|
|
58
|
+
return 'control';
|
|
267
59
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if (!variantId) {
|
|
280
|
-
console.log('[cascayd-shopify] 🆕 No stored variant, assigning new one...');
|
|
281
|
-
if (sdk && sdk.assignVariant) {
|
|
282
|
-
try {
|
|
283
|
-
console.log('[cascayd-shopify] 🎲 Calling SDK assignVariant...');
|
|
284
|
-
const result = await sdk.assignVariant(experimentId);
|
|
285
|
-
variantId = result.variantId;
|
|
286
|
-
console.log('[cascayd-shopify] ✅ SDK assigned variant:', variantId);
|
|
287
|
-
persistVariantChoice(experimentId, variantId);
|
|
288
|
-
} catch (error) {
|
|
289
|
-
console.error('[cascayd-shopify] ❌ Failed to assign variant via SDK:', error);
|
|
290
|
-
// Fallback: use first available variant or control
|
|
291
|
-
const variants = [];
|
|
292
|
-
variantElements.forEach(function(el) {
|
|
293
|
-
const vId = el.getAttribute('data-cascayd-variant');
|
|
294
|
-
if (vId && variants.indexOf(vId) === -1) {
|
|
295
|
-
variants.push(vId);
|
|
60
|
+
}
|
|
61
|
+
function showVariant(experimentId, variantId) {
|
|
62
|
+
const selector = `[data-cascayd-experiment="${experimentId}"]`;
|
|
63
|
+
const elements = document.querySelectorAll(selector);
|
|
64
|
+
elements.forEach((el) => {
|
|
65
|
+
const elVariantId = el.getAttribute('data-cascayd-variant');
|
|
66
|
+
if (elVariantId === variantId) {
|
|
67
|
+
el.classList.add('cascayd-visible');
|
|
68
|
+
const computedStyle = window.getComputedStyle(el);
|
|
69
|
+
if (computedStyle.display === 'inline' || computedStyle.display === 'inline-block') {
|
|
70
|
+
el.classList.add('cascayd-visible-inline');
|
|
296
71
|
}
|
|
297
|
-
});
|
|
298
|
-
variantId = variants.indexOf('control') >= 0 ? 'control' : (variants[0] || 'control');
|
|
299
|
-
console.log('[cascayd-shopify] 🔄 Using fallback variant:', variantId, 'from available:', variants);
|
|
300
|
-
persistVariantChoice(experimentId, variantId);
|
|
301
|
-
}
|
|
302
|
-
} else {
|
|
303
|
-
console.error('[cascayd-shopify] ❌ No assignVariant function available');
|
|
304
|
-
// This shouldn't happen since waitForSDK always returns functions (direct API or SDK)
|
|
305
|
-
}
|
|
306
|
-
} else {
|
|
307
|
-
console.log('[cascayd-shopify] ✅ Using existing variant choice:', variantId);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Show the selected variant, hide all others
|
|
311
|
-
console.log('[cascayd-shopify] 👁️ Showing/hiding variants. Selected:', variantId);
|
|
312
|
-
let hasRecorded = false;
|
|
313
|
-
variantElements.forEach(function(el, index) {
|
|
314
|
-
const elVariantId = el.getAttribute('data-cascayd-variant');
|
|
315
|
-
console.log('[cascayd-shopify] Element ' + (index + 1) + ':', { variantId: elVariantId, matches: elVariantId === variantId });
|
|
316
|
-
|
|
317
|
-
if (elVariantId === variantId) {
|
|
318
|
-
console.log('[cascayd-shopify] ✅ Showing element with variant:', elVariantId);
|
|
319
|
-
// Remove CSS hiding and mark as visible
|
|
320
|
-
el.classList.remove('cascayd-hidden');
|
|
321
|
-
el.classList.add('cascayd-visible');
|
|
322
|
-
// Detect if element should be inline or block based on original display
|
|
323
|
-
const computedStyle = window.getComputedStyle(el);
|
|
324
|
-
if (computedStyle.display === 'inline' || computedStyle.display === 'inline-block') {
|
|
325
|
-
el.classList.add('cascayd-visible-inline');
|
|
326
72
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
console.log('[cascayd-shopify] ✅ Element shown, classes:', el.className);
|
|
330
|
-
// Record impression only once for the shown variant
|
|
331
|
-
if (!hasRecorded) {
|
|
332
|
-
recordImpression(experimentId, variantId, sdk);
|
|
333
|
-
hasRecorded = true;
|
|
73
|
+
else {
|
|
74
|
+
el.classList.remove('cascayd-visible', 'cascayd-visible-inline');
|
|
334
75
|
}
|
|
335
|
-
} else {
|
|
336
|
-
console.log('[cascayd-shopify] 🚫 Hiding element with variant:', elVariantId);
|
|
337
|
-
el.style.display = 'none';
|
|
338
|
-
el.setAttribute('hidden', 'hidden');
|
|
339
|
-
el.classList.add('cascayd-hidden');
|
|
340
|
-
el.classList.remove('cascayd-visible', 'cascayd-visible-inline');
|
|
341
|
-
}
|
|
342
76
|
});
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
77
|
+
}
|
|
78
|
+
function sendImpression(experimentId, variantId) {
|
|
79
|
+
if (!variantId)
|
|
80
|
+
return;
|
|
81
|
+
const key = `${experimentId}:${variantId}`;
|
|
82
|
+
if (sentImpressions.has(key))
|
|
83
|
+
return;
|
|
84
|
+
sentImpressions.add(key);
|
|
85
|
+
void record('impression', { experimentId, variantId });
|
|
86
|
+
}
|
|
87
|
+
function findVariantIdFromElement(el, experimentId) {
|
|
88
|
+
// First try cookie (most reliable)
|
|
89
|
+
const fromCookie = readVariantChoice(experimentId);
|
|
90
|
+
if (fromCookie)
|
|
91
|
+
return fromCookie;
|
|
92
|
+
// Fallback: traverse up DOM to find parent with data-cascayd-variant
|
|
93
|
+
let current = el;
|
|
94
|
+
while (current) {
|
|
95
|
+
const variantId = current.getAttribute('data-cascayd-variant');
|
|
96
|
+
if (variantId)
|
|
97
|
+
return variantId;
|
|
98
|
+
// Check if we're inside an experiment container
|
|
99
|
+
const experimentIdAttr = current.getAttribute('data-cascayd-experiment');
|
|
100
|
+
if (experimentIdAttr === experimentId) {
|
|
101
|
+
// We're in the right experiment container, continue up
|
|
102
|
+
current = current.parentElement;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Move up to find experiment container
|
|
106
|
+
current = current.parentElement;
|
|
107
|
+
}
|
|
363
108
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
function setupConversionTracking(experimentId) {
|
|
112
|
+
const selector = `[data-cascayd-experiment="${experimentId}"] [data-cascayd-conversion]`;
|
|
113
|
+
const elements = document.querySelectorAll(selector);
|
|
114
|
+
elements.forEach((el) => {
|
|
115
|
+
if (el.hasAttribute('data-cascayd-tracked'))
|
|
116
|
+
return;
|
|
117
|
+
el.setAttribute('data-cascayd-tracked', 'true');
|
|
118
|
+
el.addEventListener('click', (e) => {
|
|
119
|
+
const variantId = findVariantIdFromElement(el, experimentId);
|
|
120
|
+
if (variantId) {
|
|
121
|
+
void record('conversion', { experimentId, variantId });
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
console.warn(`[cascayd-shopify] No variant found for experiment ${experimentId} when recording conversion`);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
370
127
|
});
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
128
|
+
}
|
|
129
|
+
async function processExperiment(experimentId) {
|
|
130
|
+
try {
|
|
131
|
+
getOrCreateSession();
|
|
132
|
+
const variantId = await assignVariantForExperiment(experimentId);
|
|
133
|
+
showVariant(experimentId, variantId);
|
|
134
|
+
sendImpression(experimentId, variantId);
|
|
135
|
+
setupConversionTracking(experimentId);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
console.error(`[cascayd-shopify] Failed to process experiment ${experimentId}`, error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function initShopifyHandler() {
|
|
142
|
+
if (typeof window === 'undefined' || typeof document === 'undefined')
|
|
379
143
|
return;
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
console.log('[cascayd-shopify] 📊 Grouped experiments:', Array.from(experiments.keys()).map(function(id) {
|
|
389
|
-
return { experimentId: id, variantCount: experiments.get(id).length };
|
|
390
|
-
}));
|
|
391
|
-
|
|
392
|
-
// Process each experiment
|
|
393
|
-
const promises = [];
|
|
394
|
-
experiments.forEach(function(variantElements, experimentId) {
|
|
395
|
-
console.log('[cascayd-shopify] 🚀 Processing experiment:', experimentId, 'with', variantElements.length, 'variants');
|
|
396
|
-
promises.push(processExperiment(experimentId, variantElements, sdk));
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
console.log('[cascayd-shopify] ⏳ Waiting for all experiments to process...');
|
|
400
|
-
await Promise.all(promises);
|
|
401
|
-
console.log('[cascayd-shopify] ✅ All experiments processed!');
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Initialize when DOM is ready
|
|
405
|
-
console.log('[cascayd-shopify] 🔄 Setting up initialization...');
|
|
406
|
-
if (document.readyState === 'loading') {
|
|
407
|
-
console.log('[cascayd-shopify] ⏳ DOM still loading, waiting for DOMContentLoaded...');
|
|
408
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
409
|
-
console.log('[cascayd-shopify] ✅ DOMContentLoaded fired!');
|
|
410
|
-
initCascaydExperiments();
|
|
411
|
-
});
|
|
412
|
-
} else {
|
|
413
|
-
// DOM already ready
|
|
414
|
-
console.log('[cascayd-shopify] ✅ DOM already ready, initializing immediately');
|
|
415
|
-
initCascaydExperiments();
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Handle Shopify's dynamic content (sections loaded via AJAX)
|
|
419
|
-
if (typeof window.addEventListener !== 'undefined') {
|
|
420
|
-
console.log('[cascayd-shopify] 🛒 Setting up Shopify section event listeners...');
|
|
421
|
-
document.addEventListener('shopify:section:load', function() {
|
|
422
|
-
console.log('[cascayd-shopify] 🔄 shopify:section:load event fired, re-initializing...');
|
|
423
|
-
initCascaydExperiments();
|
|
144
|
+
const experimentElements = document.querySelectorAll('[data-cascayd-experiment]');
|
|
145
|
+
const experimentIds = new Set();
|
|
146
|
+
experimentElements.forEach((el) => {
|
|
147
|
+
const experimentId = el.getAttribute('data-cascayd-experiment');
|
|
148
|
+
if (experimentId) {
|
|
149
|
+
experimentIds.add(experimentId);
|
|
150
|
+
}
|
|
424
151
|
});
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
initCascaydExperiments();
|
|
152
|
+
experimentIds.forEach((experimentId) => {
|
|
153
|
+
void processExperiment(experimentId);
|
|
428
154
|
});
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
155
|
+
}
|
|
156
|
+
if (typeof window !== 'undefined') {
|
|
157
|
+
if (document.readyState === 'loading') {
|
|
158
|
+
document.addEventListener('DOMContentLoaded', initShopifyHandler);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
initShopifyHandler();
|
|
162
|
+
}
|
|
163
|
+
}
|
package/dist/types.d.ts
CHANGED