@cascayd/experiment 0.3.0 → 0.3.2

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.
@@ -18,10 +18,16 @@
18
18
  (function() {
19
19
  'use strict';
20
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
+
21
25
  // Cookie helper functions (matching SDK's cookie logic)
22
26
  function getCookie(name) {
23
27
  const match = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/[.$?*|{}()\[\]\\\/\+^]/g, '\\$&') + '=([^;]*)'));
24
- return match ? decodeURIComponent(match[1]) : null;
28
+ const value = match ? decodeURIComponent(match[1]) : null;
29
+ console.log('[cascayd-shopify] đŸ“Ļ getCookie:', name, '=', value);
30
+ return value;
25
31
  }
26
32
 
27
33
  function setCookie(name, value) {
@@ -33,11 +39,15 @@
33
39
  }
34
40
 
35
41
  function readVariantChoice(experimentId) {
36
- return getCookie(getVariantCookieKey(experimentId));
42
+ const choice = getCookie(getVariantCookieKey(experimentId));
43
+ console.log('[cascayd-shopify] 📖 Read variant choice:', { experimentId, choice });
44
+ return choice;
37
45
  }
38
46
 
39
47
  function persistVariantChoice(experimentId, variantId) {
40
- setCookie(getVariantCookieKey(experimentId), variantId);
48
+ const key = getVariantCookieKey(experimentId);
49
+ console.log('[cascayd-shopify] 💾 Persisting variant choice:', { experimentId, variantId, cookieKey: key });
50
+ setCookie(key, variantId);
41
51
  }
42
52
 
43
53
  // API configuration (same as SDK)
@@ -54,16 +64,22 @@
54
64
 
55
65
  // Direct API call functions (same logic as SDK)
56
66
  async function getVariantStatus(experimentId, variantId) {
57
- const res = await fetch(
58
- BASE_URL + '/experiments/' + encodeURIComponent(experimentId) + '/variants/' + encodeURIComponent(variantId) + '/status'
59
- );
60
- const data = await res.json();
61
- if (!res.ok) {
62
- const err = new Error('Variant status failed: ' + res.status);
63
- err.payload = data;
64
- throw err;
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;
65
82
  }
66
- return data;
67
83
  }
68
84
 
69
85
  function chooseByWeight(variants) {
@@ -91,26 +107,33 @@
91
107
 
92
108
  // Assign variant using same logic as SDK
93
109
  async function assignVariantDirect(experimentId) {
110
+ console.log('[cascayd-shopify] 🎲 assignVariantDirect called for:', experimentId);
94
111
  const baseStatus = await getVariantStatus(experimentId, 'control');
112
+ console.log('[cascayd-shopify] 📊 Base status received:', baseStatus);
95
113
  const weights = baseStatus.weights || {};
96
114
  const variants = Object.entries(weights).map(function(entry) {
97
115
  return { id: entry[0], weight: entry[1] };
98
116
  });
117
+ console.log('[cascayd-shopify] 📋 Variants from weights:', variants);
99
118
 
100
119
  const allVariants = variants.length > 0 ? variants : baseStatusToVariants(baseStatus);
120
+ console.log('[cascayd-shopify] đŸŽ¯ All variants for selection:', allVariants);
101
121
  let candidate = chooseByWeight(allVariants);
122
+ console.log('[cascayd-shopify] 🎲 Selected candidate:', candidate);
102
123
  let finalStatus = null;
103
124
 
104
125
  if (candidate) {
105
126
  try {
106
127
  finalStatus = await getVariantStatus(experimentId, candidate);
128
+ console.log('[cascayd-shopify] ✅ Final status for candidate:', finalStatus);
107
129
  } catch (error) {
108
- console.error('[cascayd-shopify] getVariantStatus failed', { experimentId: experimentId, candidate: candidate, error: error });
130
+ console.error('[cascayd-shopify] ❌ getVariantStatus failed for candidate', { experimentId: experimentId, candidate: candidate, error: error });
109
131
  // Fall back to candidate
110
132
  }
111
133
  }
112
134
 
113
135
  const serving = (finalStatus && finalStatus.serving_variant_id) || candidate || 'control';
136
+ console.log('[cascayd-shopify] ✅ Final serving variant:', serving);
114
137
  return { variantId: serving };
115
138
  }
116
139
 
@@ -171,33 +194,53 @@
171
194
  async function waitForSDK(maxAttempts, interval) {
172
195
  maxAttempts = maxAttempts || 10;
173
196
  interval = interval || 100;
197
+ console.log('[cascayd-shopify] âŗ Waiting for SDK...', { maxAttempts, interval });
174
198
 
175
199
  return new Promise(function(resolve) {
176
200
  let attempts = 0;
177
201
  const checkSDK = function() {
178
202
  attempts++;
203
+ console.log('[cascayd-shopify] 🔍 Checking for SDK (attempt ' + attempts + '/' + maxAttempts + ')');
204
+
179
205
  // Check if SDK functions are available on window
180
206
  let assignVariantFn = null;
181
207
  let recordFn = null;
182
208
 
183
209
  if (typeof window !== 'undefined') {
184
- if (window.assignVariant) assignVariantFn = window.assignVariant;
185
- if (window.record) recordFn = window.record;
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
+ }
186
224
  // Try Cascayd namespace
187
225
  if (!assignVariantFn && window.Cascayd && window.Cascayd.assignVariant) {
188
226
  assignVariantFn = window.Cascayd.assignVariant;
227
+ console.log('[cascayd-shopify] ✅ Found Cascayd.assignVariant');
189
228
  }
190
229
  if (!recordFn && window.Cascayd && window.Cascayd.record) {
191
230
  recordFn = window.Cascayd.record;
231
+ console.log('[cascayd-shopify] ✅ Found Cascayd.record');
192
232
  }
193
233
  }
194
234
 
195
235
  if (assignVariantFn && recordFn) {
236
+ console.log('[cascayd-shopify] ✅ SDK functions found! Using SDK.');
196
237
  resolve({ assignVariant: assignVariantFn, record: recordFn });
197
238
  } else if (attempts >= maxAttempts) {
239
+ console.log('[cascayd-shopify] âš ī¸ SDK not found after ' + maxAttempts + ' attempts. Using direct API calls.');
198
240
  // Use direct API calls as fallback
199
241
  resolve({ assignVariant: assignVariantDirect, record: recordDirect });
200
242
  } else {
243
+ console.log('[cascayd-shopify] âŗ SDK not ready, retrying in ' + interval + 'ms...');
201
244
  setTimeout(checkSDK, interval);
202
245
  }
203
246
  };
@@ -207,8 +250,9 @@
207
250
 
208
251
  // Record impression event
209
252
  async function recordImpression(experimentId, variantId, sdk) {
253
+ console.log('[cascayd-shopify] 📝 Recording impression:', { experimentId, variantId, hasSDK: !!sdk, hasRecordFn: !!(sdk && sdk.record) });
210
254
  if (!sdk || !sdk.record) {
211
- console.debug('[cascayd-shopify] Record function not available, skipping impression tracking');
255
+ console.warn('[cascayd-shopify] âš ī¸ Record function not available, skipping impression tracking');
212
256
  return;
213
257
  }
214
258
 
@@ -217,25 +261,32 @@
217
261
  experimentId: experimentId,
218
262
  variantId: variantId
219
263
  });
264
+ console.log('[cascayd-shopify] ✅ Impression recorded successfully');
220
265
  } catch (error) {
221
- console.error('[cascayd-shopify] Failed to record impression', error);
266
+ console.error('[cascayd-shopify] ❌ Failed to record impression:', error);
222
267
  }
223
268
  }
224
269
 
225
270
  // Process a single experiment
226
271
  async function processExperiment(experimentId, variantElements, sdk) {
272
+ console.log('[cascayd-shopify] đŸ”Ŧ Processing experiment:', { experimentId, variantElementCount: variantElements.length, hasSDK: !!sdk });
273
+
227
274
  // Check if we already have a variant choice stored
228
275
  let variantId = readVariantChoice(experimentId);
276
+ console.log('[cascayd-shopify] 📖 Variant choice from cookie:', variantId);
229
277
 
230
278
  // If no stored choice, assign a variant
231
279
  if (!variantId) {
280
+ console.log('[cascayd-shopify] 🆕 No stored variant, assigning new one...');
232
281
  if (sdk && sdk.assignVariant) {
233
282
  try {
283
+ console.log('[cascayd-shopify] 🎲 Calling SDK assignVariant...');
234
284
  const result = await sdk.assignVariant(experimentId);
235
285
  variantId = result.variantId;
286
+ console.log('[cascayd-shopify] ✅ SDK assigned variant:', variantId);
236
287
  persistVariantChoice(experimentId, variantId);
237
288
  } catch (error) {
238
- console.error('[cascayd-shopify] Failed to assign variant', error);
289
+ console.error('[cascayd-shopify] ❌ Failed to assign variant via SDK:', error);
239
290
  // Fallback: use first available variant or control
240
291
  const variants = [];
241
292
  variantElements.forEach(function(el) {
@@ -245,51 +296,88 @@
245
296
  }
246
297
  });
247
298
  variantId = variants.indexOf('control') >= 0 ? 'control' : (variants[0] || 'control');
299
+ console.log('[cascayd-shopify] 🔄 Using fallback variant:', variantId, 'from available:', variants);
248
300
  persistVariantChoice(experimentId, variantId);
249
301
  }
250
302
  } else {
251
- console.error('[cascayd-shopify] No assignVariant function available');
303
+ console.error('[cascayd-shopify] ❌ No assignVariant function available');
252
304
  // This shouldn't happen since waitForSDK always returns functions (direct API or SDK)
253
305
  }
306
+ } else {
307
+ console.log('[cascayd-shopify] ✅ Using existing variant choice:', variantId);
254
308
  }
255
309
 
256
310
  // Show the selected variant, hide all others
311
+ console.log('[cascayd-shopify] đŸ‘ī¸ Showing/hiding variants. Selected:', variantId);
257
312
  let hasRecorded = false;
258
- variantElements.forEach(function(el) {
313
+ variantElements.forEach(function(el, index) {
259
314
  const elVariantId = el.getAttribute('data-cascayd-variant');
315
+ console.log('[cascayd-shopify] Element ' + (index + 1) + ':', { variantId: elVariantId, matches: elVariantId === variantId });
316
+
260
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
+ }
261
327
  el.style.display = '';
262
328
  el.removeAttribute('hidden');
329
+ console.log('[cascayd-shopify] ✅ Element shown, classes:', el.className);
263
330
  // Record impression only once for the shown variant
264
331
  if (!hasRecorded) {
265
332
  recordImpression(experimentId, variantId, sdk);
266
333
  hasRecorded = true;
267
334
  }
268
335
  } else {
336
+ console.log('[cascayd-shopify] đŸšĢ Hiding element with variant:', elVariantId);
269
337
  el.style.display = 'none';
270
338
  el.setAttribute('hidden', 'hidden');
339
+ el.classList.add('cascayd-hidden');
340
+ el.classList.remove('cascayd-visible', 'cascayd-visible-inline');
271
341
  }
272
342
  });
343
+ console.log('[cascayd-shopify] ✅ Finished processing experiment:', experimentId);
273
344
  }
274
345
 
275
346
  // Main initialization function
276
347
  async function initCascaydExperiments() {
348
+ console.log('[cascayd-shopify] đŸŽ¯ initCascaydExperiments called');
349
+ console.log('[cascayd-shopify] 📄 Document readyState:', document.readyState);
350
+
277
351
  // Wait for SDK to be loaded (with timeout)
352
+ console.log('[cascayd-shopify] âŗ Waiting for SDK...');
278
353
  const sdk = await waitForSDK(50, 100);
354
+ console.log('[cascayd-shopify] ✅ SDK ready:', { hasSDK: !!sdk, hasAssignVariant: !!(sdk && sdk.assignVariant), hasRecord: !!(sdk && sdk.record) });
279
355
 
280
356
  // Find all elements with data-cascayd-experiment attribute
281
357
  const allExperimentElements = document.querySelectorAll('[data-cascayd-experiment]');
358
+ console.log('[cascayd-shopify] 🔍 Found experiment elements:', allExperimentElements.length);
282
359
 
283
360
  if (allExperimentElements.length === 0) {
361
+ console.log('[cascayd-shopify] â„šī¸ No experiment elements found, exiting');
284
362
  return; // No experiments to process
285
363
  }
286
364
 
365
+ // Log all found elements
366
+ allExperimentElements.forEach(function(el, index) {
367
+ const expId = el.getAttribute('data-cascayd-experiment');
368
+ const varId = el.getAttribute('data-cascayd-variant');
369
+ console.log('[cascayd-shopify] Element ' + (index + 1) + ':', { experimentId: expId, variantId: varId, tagName: el.tagName });
370
+ });
371
+
287
372
  // Group elements by experiment ID
288
373
  const experiments = new Map();
289
374
 
290
375
  allExperimentElements.forEach(function(el) {
291
376
  const experimentId = el.getAttribute('data-cascayd-experiment');
292
- if (!experimentId) return;
377
+ if (!experimentId) {
378
+ console.warn('[cascayd-shopify] âš ī¸ Element missing experiment ID:', el);
379
+ return;
380
+ }
293
381
 
294
382
  if (!experiments.has(experimentId)) {
295
383
  experiments.set(experimentId, []);
@@ -297,27 +385,49 @@
297
385
  experiments.get(experimentId).push(el);
298
386
  });
299
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
+
300
392
  // Process each experiment
301
393
  const promises = [];
302
394
  experiments.forEach(function(variantElements, experimentId) {
395
+ console.log('[cascayd-shopify] 🚀 Processing experiment:', experimentId, 'with', variantElements.length, 'variants');
303
396
  promises.push(processExperiment(experimentId, variantElements, sdk));
304
397
  });
305
398
 
399
+ console.log('[cascayd-shopify] âŗ Waiting for all experiments to process...');
306
400
  await Promise.all(promises);
401
+ console.log('[cascayd-shopify] ✅ All experiments processed!');
307
402
  }
308
403
 
309
404
  // Initialize when DOM is ready
405
+ console.log('[cascayd-shopify] 🔄 Setting up initialization...');
310
406
  if (document.readyState === 'loading') {
311
- document.addEventListener('DOMContentLoaded', initCascaydExperiments);
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
+ });
312
412
  } else {
313
413
  // DOM already ready
414
+ console.log('[cascayd-shopify] ✅ DOM already ready, initializing immediately');
314
415
  initCascaydExperiments();
315
416
  }
316
417
 
317
418
  // Handle Shopify's dynamic content (sections loaded via AJAX)
318
419
  if (typeof window.addEventListener !== 'undefined') {
319
- document.addEventListener('shopify:section:load', initCascaydExperiments);
320
- document.addEventListener('shopify:section:reorder', initCascaydExperiments);
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();
424
+ });
425
+ document.addEventListener('shopify:section:reorder', function() {
426
+ console.log('[cascayd-shopify] 🔄 shopify:section:reorder event fired, re-initializing...');
427
+ initCascaydExperiments();
428
+ });
321
429
  }
430
+
431
+ console.log('[cascayd-shopify] ✅ Handler script initialization complete');
322
432
  })();
323
433
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cascayd/experiment",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "A lightweight A/B testing SDK for React applications with server-side analytics integration",
5
5
  "keywords": [
6
6
  "ab-testing",