@keak/sdk 2.1.2 → 2.1.4
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/Conversion.d.ts +4 -0
- package/dist/Conversion.d.ts.map +1 -1
- package/dist/index.cjs.js +38 -501
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +0 -65
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -501
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1616,138 +1616,15 @@ var KeakToolbar$1 = /*#__PURE__*/Object.freeze({
|
|
|
1616
1616
|
KeakToolbar: KeakToolbar
|
|
1617
1617
|
});
|
|
1618
1618
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
// Skip tracking in production
|
|
1626
|
-
if (isProduction || disabled)
|
|
1627
|
-
return;
|
|
1628
|
-
// Track conversion element exposure (event tracking is disabled in SDK, this is a no-op)
|
|
1629
|
-
keak.track('conversion_exposure', {
|
|
1630
|
-
conversionId: conversionId.current,
|
|
1631
|
-
type,
|
|
1632
|
-
category,
|
|
1633
|
-
label,
|
|
1634
|
-
metadata: {
|
|
1635
|
-
...metadata,
|
|
1636
|
-
timestamp: new Date().toISOString(),
|
|
1637
|
-
url: window.location.href,
|
|
1638
|
-
referrer: document.referrer
|
|
1639
|
-
}
|
|
1640
|
-
});
|
|
1641
|
-
}, [isProduction, type, category, label, metadata, disabled]);
|
|
1642
|
-
const handleClick = (event) => {
|
|
1643
|
-
if (disabled || isProduction)
|
|
1644
|
-
return;
|
|
1645
|
-
// Extract additional context from the clicked element
|
|
1646
|
-
event.currentTarget;
|
|
1647
|
-
const clickedElement = event.target;
|
|
1648
|
-
// Determine the actual clicked element type and content
|
|
1649
|
-
let elementType = type;
|
|
1650
|
-
let elementText = '';
|
|
1651
|
-
let elementTag = '';
|
|
1652
|
-
let href = '';
|
|
1653
|
-
if (clickedElement) {
|
|
1654
|
-
elementTag = clickedElement.tagName.toLowerCase();
|
|
1655
|
-
elementText = clickedElement.textContent?.trim() || clickedElement.getAttribute('value') || clickedElement.getAttribute('alt') || '';
|
|
1656
|
-
// Auto-detect element type if not specified
|
|
1657
|
-
if (type === 'custom') {
|
|
1658
|
-
if (elementTag === 'button' || clickedElement.getAttribute('role') === 'button') {
|
|
1659
|
-
elementType = 'button';
|
|
1660
|
-
}
|
|
1661
|
-
else if (elementTag === 'a') {
|
|
1662
|
-
elementType = 'link';
|
|
1663
|
-
href = clickedElement.getAttribute('href') || '';
|
|
1664
|
-
}
|
|
1665
|
-
else if (elementTag === 'input' && (clickedElement.getAttribute('type') === 'submit' || clickedElement.getAttribute('type') === 'button')) {
|
|
1666
|
-
elementType = 'button';
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
// Auto-detect category based on text content if not specified
|
|
1670
|
-
if (!category && elementText) {
|
|
1671
|
-
const text = elementText.toLowerCase();
|
|
1672
|
-
if (/buy|purchase|checkout|order|cart/.test(text)) {
|
|
1673
|
-
category = 'purchase';
|
|
1674
|
-
}
|
|
1675
|
-
else if (/sign.?up|register|join/.test(text)) {
|
|
1676
|
-
category = 'signup';
|
|
1677
|
-
}
|
|
1678
|
-
else if (/download|get.?app/.test(text)) {
|
|
1679
|
-
category = 'download';
|
|
1680
|
-
}
|
|
1681
|
-
else if (/demo|trial|preview/.test(text)) {
|
|
1682
|
-
category = 'demo';
|
|
1683
|
-
}
|
|
1684
|
-
else if (/contact|call|phone/.test(text)) {
|
|
1685
|
-
category = 'contact';
|
|
1686
|
-
}
|
|
1687
|
-
else if (/learn.?more|read.?more/.test(text)) {
|
|
1688
|
-
category = 'engagement';
|
|
1689
|
-
}
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
// Track the conversion with variant snapshot
|
|
1693
|
-
const conversionName = `${category || 'uncategorized'}_${elementType}`;
|
|
1694
|
-
const conversionValue = value || 1;
|
|
1695
|
-
const additionalData = {
|
|
1696
|
-
conversionId: conversionId.current,
|
|
1697
|
-
type: elementType,
|
|
1698
|
-
category: category || 'uncategorized',
|
|
1699
|
-
label: label || elementText || 'unlabeled',
|
|
1700
|
-
elementDetails: {
|
|
1701
|
-
tag: elementTag,
|
|
1702
|
-
text: elementText,
|
|
1703
|
-
href,
|
|
1704
|
-
className: clickedElement.className,
|
|
1705
|
-
id: clickedElement.id
|
|
1706
|
-
},
|
|
1707
|
-
...metadata,
|
|
1708
|
-
viewport: {
|
|
1709
|
-
width: window.innerWidth,
|
|
1710
|
-
height: window.innerHeight
|
|
1711
|
-
},
|
|
1712
|
-
scroll: {
|
|
1713
|
-
x: window.scrollX,
|
|
1714
|
-
y: window.scrollY
|
|
1715
|
-
},
|
|
1716
|
-
referrer: document.referrer
|
|
1717
|
-
};
|
|
1718
|
-
// Use the new snapshot-based conversion tracking
|
|
1719
|
-
keak.trackConversion(conversionName, conversionValue, additionalData);
|
|
1720
|
-
// Optional: Add visual feedback for debugging in development
|
|
1721
|
-
if (process.env.NODE_ENV === 'development') {
|
|
1722
|
-
console.log('🎯 Conversion snapshot tracked:', {
|
|
1723
|
-
id: conversionId.current,
|
|
1724
|
-
conversionName,
|
|
1725
|
-
conversionValue,
|
|
1726
|
-
type: elementType,
|
|
1727
|
-
category: category || 'uncategorized',
|
|
1728
|
-
label: label || elementText || 'unlabeled',
|
|
1729
|
-
element: elementTag,
|
|
1730
|
-
snapshotApproach: true
|
|
1731
|
-
});
|
|
1732
|
-
// Show brief visual feedback
|
|
1733
|
-
const element = elementRef.current;
|
|
1734
|
-
if (element) {
|
|
1735
|
-
const originalTransition = element.style.transition;
|
|
1736
|
-
element.style.transition = 'all 0.2s ease';
|
|
1737
|
-
element.style.transform = 'scale(0.98)';
|
|
1738
|
-
setTimeout(() => {
|
|
1739
|
-
element.style.transform = 'scale(1)';
|
|
1740
|
-
setTimeout(() => {
|
|
1741
|
-
element.style.transition = originalTransition;
|
|
1742
|
-
}, 200);
|
|
1743
|
-
}, 100);
|
|
1744
|
-
}
|
|
1745
|
-
}
|
|
1746
|
-
};
|
|
1747
|
-
return (jsx("div", { ref: elementRef, onClick: handleClick, style: {
|
|
1619
|
+
/**
|
|
1620
|
+
* Conversion component - renders children without tracking
|
|
1621
|
+
* Tracking is handled by the embedded script, not the SDK
|
|
1622
|
+
*/
|
|
1623
|
+
const Conversion = ({ children, type = 'custom', category, disabled = false }) => {
|
|
1624
|
+
return (jsx("div", { style: {
|
|
1748
1625
|
display: 'contents', // This makes the wrapper invisible to CSS layout
|
|
1749
1626
|
pointerEvents: disabled ? 'none' : 'auto'
|
|
1750
|
-
}, "data-keak-conversion-
|
|
1627
|
+
}, "data-keak-conversion-type": type, "data-keak-conversion-category": category, children: children }));
|
|
1751
1628
|
};
|
|
1752
1629
|
|
|
1753
1630
|
function sourcePathInjectionRuntime(sourceMapping) {
|
|
@@ -2198,23 +2075,18 @@ let toolbarInitialized = false;
|
|
|
2198
2075
|
class KeakSDK {
|
|
2199
2076
|
constructor(config) {
|
|
2200
2077
|
this.assignments = {};
|
|
2201
|
-
this.eventQueue = [];
|
|
2202
2078
|
this.initialized = false;
|
|
2203
2079
|
this.liveExperiments = new Map();
|
|
2204
|
-
this.impressions = new Map(); // experimentId -> variantName -> count
|
|
2205
2080
|
this.config = config;
|
|
2206
2081
|
this.userId = config.userId || this.generateUserId();
|
|
2207
2082
|
this.sessionId = config.sessionId || this.generateSessionId();
|
|
2208
2083
|
this.deviceId = this.generateDeviceId();
|
|
2209
|
-
this.loadImpressions();
|
|
2210
2084
|
}
|
|
2211
2085
|
async init() {
|
|
2212
2086
|
try {
|
|
2213
2087
|
// Use empty assignments - no backend integration
|
|
2214
2088
|
this.assignments = {};
|
|
2215
2089
|
this.initialized = true;
|
|
2216
|
-
// Setup automatic event tracking
|
|
2217
|
-
this.setupAutoTracking();
|
|
2218
2090
|
if (this.config.debug) {
|
|
2219
2091
|
console.log('Keak SDK initialized successfully', {
|
|
2220
2092
|
assignments: this.assignments,
|
|
@@ -2227,99 +2099,6 @@ class KeakSDK {
|
|
|
2227
2099
|
this.initialized = true; // Continue with empty assignments
|
|
2228
2100
|
}
|
|
2229
2101
|
}
|
|
2230
|
-
getVariant(experimentKey, availableVariants) {
|
|
2231
|
-
// Use local assignment only
|
|
2232
|
-
if (availableVariants && availableVariants.length > 0) {
|
|
2233
|
-
const localAssignment = this.assignVariant(experimentKey, availableVariants);
|
|
2234
|
-
return localAssignment;
|
|
2235
|
-
}
|
|
2236
|
-
// Fallback to control
|
|
2237
|
-
return 'control';
|
|
2238
|
-
}
|
|
2239
|
-
trackEvent(eventName, properties = {}) {
|
|
2240
|
-
// Event tracking disabled - dashboard only receives impression data
|
|
2241
|
-
if (this.config.debug) {
|
|
2242
|
-
console.log(`🔇 [Keak SDK] Event tracking disabled: ${eventName}`, properties);
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
trackClick(elementId, text) {
|
|
2246
|
-
// Click tracking disabled - dashboard only receives impression data
|
|
2247
|
-
if (this.config.debug) {
|
|
2248
|
-
console.log(`🔇 [Keak SDK] Click tracking disabled: ${elementId}`, text);
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
trackConversion(conversionName, value, additionalData) {
|
|
2252
|
-
const snapshot = this.takeVariantSnapshot(conversionName, value, additionalData);
|
|
2253
|
-
if (this.config.debug) {
|
|
2254
|
-
console.log(`🎯 [Keak SDK] Conversion tracked: ${conversionName}`, {
|
|
2255
|
-
value,
|
|
2256
|
-
snapshot: snapshot.activeVariants,
|
|
2257
|
-
totalExperiments: Object.keys(snapshot.activeVariants).length
|
|
2258
|
-
});
|
|
2259
|
-
}
|
|
2260
|
-
}
|
|
2261
|
-
// Take a snapshot of all currently active variants when conversion happens
|
|
2262
|
-
takeVariantSnapshot(conversionName, value, additionalData) {
|
|
2263
|
-
const activeVariants = {};
|
|
2264
|
-
const experimentMetadata = {};
|
|
2265
|
-
// Capture all live experiments and their current variant assignments
|
|
2266
|
-
this.liveExperiments.forEach((experiment, experimentId) => {
|
|
2267
|
-
const currentVariant = this.getVariant(experimentId, experiment.variants?.map((v) => v.name));
|
|
2268
|
-
activeVariants[experimentId] = currentVariant;
|
|
2269
|
-
experimentMetadata[experimentId] = {
|
|
2270
|
-
name: experiment.name,
|
|
2271
|
-
totalVariants: experiment.totalVariants,
|
|
2272
|
-
variants: experiment.variants,
|
|
2273
|
-
pageUrl: experiment.pageUrl
|
|
2274
|
-
};
|
|
2275
|
-
});
|
|
2276
|
-
// Capture visitor metadata at conversion time
|
|
2277
|
-
const visitorMetadata = this.captureVisitorMetadata();
|
|
2278
|
-
const snapshot = {
|
|
2279
|
-
conversionId: `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
2280
|
-
conversionName,
|
|
2281
|
-
conversionValue: value,
|
|
2282
|
-
userId: this.userId,
|
|
2283
|
-
sessionId: this.sessionId,
|
|
2284
|
-
timestamp: new Date().toISOString(),
|
|
2285
|
-
activeVariants,
|
|
2286
|
-
experimentMetadata,
|
|
2287
|
-
pageUrl: window.location.href,
|
|
2288
|
-
visitorMetadata, // NEW: Include visitor metadata
|
|
2289
|
-
additionalData: additionalData || {}
|
|
2290
|
-
};
|
|
2291
|
-
// Store conversion locally as backup
|
|
2292
|
-
this.storeConversionLocally(snapshot);
|
|
2293
|
-
return snapshot;
|
|
2294
|
-
}
|
|
2295
|
-
// Store conversion snapshot locally
|
|
2296
|
-
storeConversionLocally(snapshot) {
|
|
2297
|
-
try {
|
|
2298
|
-
const stored = localStorage.getItem('keak_conversions') || '[]';
|
|
2299
|
-
const conversions = JSON.parse(stored);
|
|
2300
|
-
conversions.push(snapshot);
|
|
2301
|
-
// Keep only last 100 conversions to prevent storage bloat
|
|
2302
|
-
if (conversions.length > 100) {
|
|
2303
|
-
conversions.splice(0, conversions.length - 100);
|
|
2304
|
-
}
|
|
2305
|
-
localStorage.setItem('keak_conversions', JSON.stringify(conversions));
|
|
2306
|
-
}
|
|
2307
|
-
catch (error) {
|
|
2308
|
-
if (this.config.debug) {
|
|
2309
|
-
console.warn('Failed to store conversion locally:', error);
|
|
2310
|
-
}
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
setupAutoTracking() {
|
|
2314
|
-
// Auto-tracking disabled
|
|
2315
|
-
if (this.config.debug) {
|
|
2316
|
-
console.log('🔇 [Keak SDK] Auto-tracking disabled');
|
|
2317
|
-
}
|
|
2318
|
-
}
|
|
2319
|
-
flushEvents() {
|
|
2320
|
-
// No event queue to flush - events are disabled
|
|
2321
|
-
this.eventQueue = [];
|
|
2322
|
-
}
|
|
2323
2102
|
generateUserId() {
|
|
2324
2103
|
// Try to get existing user ID from localStorage
|
|
2325
2104
|
const existing = localStorage.getItem('keak_user_id');
|
|
@@ -2342,194 +2121,16 @@ class KeakSDK {
|
|
|
2342
2121
|
localStorage.setItem('keak_device_id', deviceId.toString());
|
|
2343
2122
|
return deviceId.toString();
|
|
2344
2123
|
}
|
|
2345
|
-
// Capture visitor metadata at the moment of event
|
|
2346
|
-
captureVisitorMetadata() {
|
|
2347
|
-
const ua = navigator.userAgent;
|
|
2348
|
-
const platform = navigator.platform;
|
|
2349
|
-
// Detect OS
|
|
2350
|
-
let os = 'Unknown';
|
|
2351
|
-
if (/iPhone/.test(ua))
|
|
2352
|
-
os = 'iPhone';
|
|
2353
|
-
else if (/iPad/.test(ua))
|
|
2354
|
-
os = 'iPad';
|
|
2355
|
-
else if (/Android/.test(ua))
|
|
2356
|
-
os = 'Android';
|
|
2357
|
-
else if (platform.includes('Win'))
|
|
2358
|
-
os = 'Windows';
|
|
2359
|
-
else if (platform.includes('Mac'))
|
|
2360
|
-
os = 'macOS';
|
|
2361
|
-
else if (platform.includes('Linux'))
|
|
2362
|
-
os = 'Linux';
|
|
2363
|
-
else
|
|
2364
|
-
os = platform || 'Unknown';
|
|
2365
|
-
// Detect browser
|
|
2366
|
-
let browser = 'Unknown';
|
|
2367
|
-
if (ua.includes('Edg/'))
|
|
2368
|
-
browser = 'Edge';
|
|
2369
|
-
else if (ua.includes('Chrome') && !ua.includes('Edg'))
|
|
2370
|
-
browser = 'Chrome';
|
|
2371
|
-
else if (ua.includes('Safari') && !ua.includes('Chrome'))
|
|
2372
|
-
browser = 'Safari';
|
|
2373
|
-
else if (ua.includes('Firefox'))
|
|
2374
|
-
browser = 'Firefox';
|
|
2375
|
-
// Get location from localStorage cache (updated by MetricsPanel)
|
|
2376
|
-
let country = 'Unknown';
|
|
2377
|
-
let ipAddress = 'Unknown';
|
|
2378
|
-
try {
|
|
2379
|
-
const locationCache = localStorage.getItem('keak_location_cache');
|
|
2380
|
-
if (locationCache) {
|
|
2381
|
-
const location = JSON.parse(locationCache);
|
|
2382
|
-
country = location.country || 'Unknown';
|
|
2383
|
-
ipAddress = location.ipAddress || 'Unknown';
|
|
2384
|
-
}
|
|
2385
|
-
}
|
|
2386
|
-
catch (error) {
|
|
2387
|
-
// Ignore parsing errors
|
|
2388
|
-
}
|
|
2389
|
-
return {
|
|
2390
|
-
location: {
|
|
2391
|
-
country,
|
|
2392
|
-
ipAddress
|
|
2393
|
-
},
|
|
2394
|
-
device: {
|
|
2395
|
-
os,
|
|
2396
|
-
browser,
|
|
2397
|
-
screenSize: `${window.screen.width} × ${window.screen.height}`,
|
|
2398
|
-
viewport: `${window.innerWidth} × ${window.innerHeight}`,
|
|
2399
|
-
userAgent: ua
|
|
2400
|
-
},
|
|
2401
|
-
session: {
|
|
2402
|
-
userId: this.userId,
|
|
2403
|
-
deviceId: this.deviceId,
|
|
2404
|
-
sessionId: this.sessionId,
|
|
2405
|
-
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'Unknown',
|
|
2406
|
-
language: navigator.language || 'Unknown'
|
|
2407
|
-
}
|
|
2408
|
-
};
|
|
2409
|
-
}
|
|
2410
|
-
// Simple hash function for deterministic randomization
|
|
2411
|
-
simpleHash(str) {
|
|
2412
|
-
let hash = 0;
|
|
2413
|
-
for (let i = 0; i < str.length; i++) {
|
|
2414
|
-
hash = ((hash << 5) - hash + str.charCodeAt(i)) & 0xffffffff;
|
|
2415
|
-
}
|
|
2416
|
-
return Math.abs(hash);
|
|
2417
|
-
}
|
|
2418
|
-
// Get or create variant assignment for user
|
|
2419
|
-
assignVariant(experimentId, variants) {
|
|
2420
|
-
const storageKey = `keak_assignment_${experimentId}`;
|
|
2421
|
-
const stored = localStorage.getItem(storageKey);
|
|
2422
|
-
if (stored && variants.includes(stored)) {
|
|
2423
|
-
return stored;
|
|
2424
|
-
}
|
|
2425
|
-
// Deterministic randomization based on userId + experimentId
|
|
2426
|
-
const hash = this.simpleHash(`${this.userId}_${experimentId}`);
|
|
2427
|
-
const selectedVariant = variants[hash % variants.length];
|
|
2428
|
-
try {
|
|
2429
|
-
localStorage.setItem(storageKey, selectedVariant);
|
|
2430
|
-
}
|
|
2431
|
-
catch (error) {
|
|
2432
|
-
if (this.config.debug) {
|
|
2433
|
-
console.warn('Failed to store variant assignment:', error);
|
|
2434
|
-
}
|
|
2435
|
-
}
|
|
2436
|
-
if (this.config.debug) {
|
|
2437
|
-
console.log(`🎯 [Keak SDK] Assigned variant "${selectedVariant}" for experiment "${experimentId}" to user ${this.userId}`);
|
|
2438
|
-
}
|
|
2439
|
-
return selectedVariant;
|
|
2440
|
-
}
|
|
2441
|
-
// Impression tracking methods
|
|
2442
|
-
loadImpressions() {
|
|
2443
|
-
try {
|
|
2444
|
-
const stored = localStorage.getItem('keak_impressions');
|
|
2445
|
-
if (stored) {
|
|
2446
|
-
const data = JSON.parse(stored);
|
|
2447
|
-
this.impressions = new Map(Object.entries(data).map(([experimentId, variants]) => [
|
|
2448
|
-
experimentId,
|
|
2449
|
-
new Map(Object.entries(variants))
|
|
2450
|
-
]));
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
catch (error) {
|
|
2454
|
-
if (this.config.debug) {
|
|
2455
|
-
console.warn('Failed to load impressions from localStorage:', error);
|
|
2456
|
-
}
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2459
|
-
saveImpressions() {
|
|
2460
|
-
try {
|
|
2461
|
-
const data = {};
|
|
2462
|
-
this.impressions.forEach((variants, experimentId) => {
|
|
2463
|
-
data[experimentId] = Object.fromEntries(variants);
|
|
2464
|
-
});
|
|
2465
|
-
localStorage.setItem('keak_impressions', JSON.stringify(data));
|
|
2466
|
-
}
|
|
2467
|
-
catch (error) {
|
|
2468
|
-
if (this.config.debug) {
|
|
2469
|
-
console.warn('Failed to save impressions to localStorage:', error);
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
}
|
|
2473
|
-
trackImpression(experimentId, variantName) {
|
|
2474
|
-
if (!this.impressions.has(experimentId)) {
|
|
2475
|
-
this.impressions.set(experimentId, new Map());
|
|
2476
|
-
}
|
|
2477
|
-
const variants = this.impressions.get(experimentId);
|
|
2478
|
-
const currentCount = variants.get(variantName) || 0;
|
|
2479
|
-
variants.set(variantName, currentCount + 1);
|
|
2480
|
-
this.saveImpressions();
|
|
2481
|
-
// Store impression with visitor metadata
|
|
2482
|
-
this.storeImpressionWithMetadata(experimentId, variantName);
|
|
2483
|
-
}
|
|
2484
|
-
storeImpressionWithMetadata(experimentId, variantName) {
|
|
2485
|
-
try {
|
|
2486
|
-
const visitorMetadata = this.captureVisitorMetadata();
|
|
2487
|
-
const impressionEvent = {
|
|
2488
|
-
experimentId,
|
|
2489
|
-
variantName,
|
|
2490
|
-
timestamp: new Date().toISOString(),
|
|
2491
|
-
pageUrl: window.location.href,
|
|
2492
|
-
visitorMetadata
|
|
2493
|
-
};
|
|
2494
|
-
// Store in separate detailed impressions log
|
|
2495
|
-
const stored = localStorage.getItem('keak_impressions_detailed') || '[]';
|
|
2496
|
-
const impressions = JSON.parse(stored);
|
|
2497
|
-
impressions.push(impressionEvent);
|
|
2498
|
-
// Keep only last 1000 impressions to prevent storage bloat
|
|
2499
|
-
if (impressions.length > 1000) {
|
|
2500
|
-
impressions.splice(0, impressions.length - 1000);
|
|
2501
|
-
}
|
|
2502
|
-
localStorage.setItem('keak_impressions_detailed', JSON.stringify(impressions));
|
|
2503
|
-
}
|
|
2504
|
-
catch (error) {
|
|
2505
|
-
if (this.config.debug) {
|
|
2506
|
-
console.warn('Failed to store impression metadata:', error);
|
|
2507
|
-
}
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
getImpressions(experimentId) {
|
|
2511
|
-
const variants = this.impressions.get(experimentId);
|
|
2512
|
-
return variants ? Object.fromEntries(variants) : {};
|
|
2513
|
-
}
|
|
2514
|
-
getAllImpressions() {
|
|
2515
|
-
const data = {};
|
|
2516
|
-
this.impressions.forEach((variants, experimentId) => {
|
|
2517
|
-
data[experimentId] = Object.fromEntries(variants);
|
|
2518
|
-
});
|
|
2519
|
-
return data;
|
|
2520
|
-
}
|
|
2521
2124
|
// Register live experiment
|
|
2522
2125
|
registerLiveExperiment(experimentData) {
|
|
2523
|
-
const impressions = this.getImpressions(experimentData.experimentId);
|
|
2524
2126
|
this.liveExperiments.set(experimentData.experimentId, {
|
|
2525
2127
|
...experimentData,
|
|
2526
2128
|
isActive: true,
|
|
2527
2129
|
registeredAt: new Date().toISOString(),
|
|
2528
|
-
currentVariant: this.assignments[experimentData.experimentId] || experimentData.variants[0]?.name || 'control'
|
|
2529
|
-
impressions: impressions
|
|
2130
|
+
currentVariant: this.assignments[experimentData.experimentId] || experimentData.variants[0]?.name || 'control'
|
|
2530
2131
|
});
|
|
2531
2132
|
if (this.config.debug) {
|
|
2532
|
-
console.log('📋 [Keak SDK] Live experiment registered:', experimentData.experimentId
|
|
2133
|
+
console.log('📋 [Keak SDK] Live experiment registered:', experimentData.experimentId);
|
|
2533
2134
|
}
|
|
2534
2135
|
}
|
|
2535
2136
|
// Get all live experiments for management panel
|
|
@@ -2543,7 +2144,6 @@ class KeakSDK {
|
|
|
2543
2144
|
const experiment = this.liveExperiments.get(experimentId);
|
|
2544
2145
|
if (experiment) {
|
|
2545
2146
|
experiment.currentVariant = variant;
|
|
2546
|
-
experiment.impressions = this.getImpressions(experimentId); // Update impressions
|
|
2547
2147
|
this.liveExperiments.set(experimentId, experiment);
|
|
2548
2148
|
}
|
|
2549
2149
|
if (this.config.debug) {
|
|
@@ -2756,14 +2356,26 @@ function detectVariantElements(children) {
|
|
|
2756
2356
|
}
|
|
2757
2357
|
// Experiment Component
|
|
2758
2358
|
const Experiment = ({ name, children, active = true }) => {
|
|
2759
|
-
//
|
|
2760
|
-
|
|
2761
|
-
|
|
2359
|
+
// CRITICAL: For Keak Code desktop projects, check if embedded script is loaded
|
|
2360
|
+
// This must be SYNCHRONOUS to prevent both variants from rendering (flicker)
|
|
2361
|
+
if (typeof window !== 'undefined') {
|
|
2362
|
+
const embeddedData = window.KEAK_EMBEDDED_DATA;
|
|
2363
|
+
const isDesktopProject = embeddedData && embeddedData.projectUuid;
|
|
2364
|
+
// If this is a desktop project, verify the script is fully loaded
|
|
2365
|
+
if (isDesktopProject) {
|
|
2366
|
+
const hasComputeFunction = typeof window.KeakAssign?.computeAssignments === 'function';
|
|
2367
|
+
if (!hasComputeFunction) {
|
|
2368
|
+
// Script not ready yet - return null to prevent flicker
|
|
2369
|
+
console.log(`[Keak SDK] Waiting for embedded script to load for "${name}"...`);
|
|
2370
|
+
return null;
|
|
2371
|
+
}
|
|
2372
|
+
console.log(`[Keak SDK] Embedded script ready for "${name}"`);
|
|
2373
|
+
}
|
|
2762
2374
|
}
|
|
2763
|
-
// Access global SDK instance directly (no context needed)
|
|
2764
|
-
const assignments = keakInstance?.assignments || {};
|
|
2765
2375
|
// Use enhanced variant detection
|
|
2766
2376
|
const variantElements = detectVariantElements(children);
|
|
2377
|
+
// Access global SDK instance directly (no context needed)
|
|
2378
|
+
const assignments = keakInstance?.assignments || {};
|
|
2767
2379
|
// Extract comprehensive variant metadata
|
|
2768
2380
|
const variantMetadata = variantElements.map(variant => {
|
|
2769
2381
|
const variantContent = extractVariantContent(variant.props.children);
|
|
@@ -2784,7 +2396,7 @@ const Experiment = ({ name, children, active = true }) => {
|
|
|
2784
2396
|
window.location.hostname === '127.0.0.1' ||
|
|
2785
2397
|
window.location.port !== '' ||
|
|
2786
2398
|
keakInstance?.config.debug);
|
|
2787
|
-
// Register experiment with SDK for live management
|
|
2399
|
+
// Register experiment with SDK for live management (for toolbar)
|
|
2788
2400
|
useEffect(() => {
|
|
2789
2401
|
if (keakInstance && variantElements.length > 0) {
|
|
2790
2402
|
// Register experiment with enhanced metadata
|
|
@@ -2805,39 +2417,10 @@ const Experiment = ({ name, children, active = true }) => {
|
|
|
2805
2417
|
};
|
|
2806
2418
|
// Register live experiment for management panel
|
|
2807
2419
|
keakInstance.registerLiveExperiment(experimentData);
|
|
2808
|
-
// Only track impressions if experiment is active
|
|
2809
|
-
if (active) {
|
|
2810
|
-
const variantNames = variantMetadata.map(v => v.name);
|
|
2811
|
-
// In dev mode, don't call getVariant (which stores in localStorage)
|
|
2812
|
-
// Instead, use "treatment" for tracking purposes
|
|
2813
|
-
let assignedVariant;
|
|
2814
|
-
if (isDevMode) {
|
|
2815
|
-
assignedVariant = variantNames.includes('treatment') ? 'treatment' : variantNames[0];
|
|
2816
|
-
if (keakInstance.config.debug) {
|
|
2817
|
-
console.log(`🎯 [Keak SDK] Experiment "${name}" in dev mode, using "${assignedVariant}" for tracking (not stored)`);
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
else {
|
|
2821
|
-
// Production mode: use normal assignment (stores in localStorage)
|
|
2822
|
-
assignedVariant = keakInstance.getVariant(name, variantNames);
|
|
2823
|
-
if (keakInstance.config.debug) {
|
|
2824
|
-
console.log(`🎯 [Keak SDK] Split test assignment for "${name}": → Variant "${assignedVariant}"`);
|
|
2825
|
-
}
|
|
2826
|
-
}
|
|
2827
|
-
// Track impression immediately when variant is determined
|
|
2828
|
-
if (assignedVariant) {
|
|
2829
|
-
keakInstance.trackImpression(name, assignedVariant);
|
|
2830
|
-
}
|
|
2831
|
-
}
|
|
2832
|
-
else {
|
|
2833
|
-
if (keakInstance.config.debug) {
|
|
2834
|
-
console.log(`🔇 [Keak SDK] Experiment "${name}" is inactive, not tracking impressions`);
|
|
2835
|
-
}
|
|
2836
|
-
}
|
|
2837
2420
|
}
|
|
2838
|
-
}, [name, variantElements.length, variantMetadata, active
|
|
2421
|
+
}, [name, variantElements.length, variantMetadata, active]);
|
|
2839
2422
|
// Get variant assignment from SDK (with proper split testing)
|
|
2840
|
-
|
|
2423
|
+
variantMetadata.map(v => v.name);
|
|
2841
2424
|
let selectedIndex;
|
|
2842
2425
|
// Priority 1: Check for forced variant from setExperimentVariant (highest priority)
|
|
2843
2426
|
const forcedVariant = keakInstance?.assignments[name] || assignments[name];
|
|
@@ -2904,16 +2487,14 @@ const Experiment = ({ name, children, active = true }) => {
|
|
|
2904
2487
|
}
|
|
2905
2488
|
}
|
|
2906
2489
|
else {
|
|
2907
|
-
// Active in production:
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
console.warn(`⚠️ [Keak SDK] Assigned variant "${assignedVariantName}" not found, using first variant`);
|
|
2916
|
-
}
|
|
2490
|
+
// Active in production: Show control (original) by default
|
|
2491
|
+
// Embedded script will handle A/B assignment if present
|
|
2492
|
+
selectedIndex = variantElements.findIndex((variant) => variant.props.name === 'control');
|
|
2493
|
+
if (selectedIndex === -1) {
|
|
2494
|
+
selectedIndex = 0; // Fallback to first variant if no "control" named variant
|
|
2495
|
+
}
|
|
2496
|
+
if (keakInstance?.config.debug) {
|
|
2497
|
+
console.log(`🎯 [Keak SDK] Experiment "${name}" in production, showing control (embedded script handles A/B)`);
|
|
2917
2498
|
}
|
|
2918
2499
|
}
|
|
2919
2500
|
}
|
|
@@ -2992,33 +2573,7 @@ const keak = {
|
|
|
2992
2573
|
}
|
|
2993
2574
|
return Promise.resolve();
|
|
2994
2575
|
},
|
|
2995
|
-
|
|
2996
|
-
if (!keakInstance) {
|
|
2997
|
-
console.warn('Keak SDK not initialized. Call keak.init() first.');
|
|
2998
|
-
return 'control';
|
|
2999
|
-
}
|
|
3000
|
-
return keakInstance.getVariant(experimentKey, availableVariants);
|
|
3001
|
-
},
|
|
3002
|
-
track: (eventName, properties) => {
|
|
3003
|
-
// Event tracking disabled - dashboard only receives impression data
|
|
3004
|
-
if (keakInstance?.config.debug) {
|
|
3005
|
-
console.log(`🔇 [Keak SDK] Global event tracking disabled: ${eventName}`, properties);
|
|
3006
|
-
}
|
|
3007
|
-
},
|
|
3008
|
-
trackClick: (elementId, text) => {
|
|
3009
|
-
// Click tracking disabled - dashboard only receives impression data
|
|
3010
|
-
if (keakInstance?.config.debug) {
|
|
3011
|
-
console.log(`🔇 [Keak SDK] Global click tracking disabled: ${elementId}`, text);
|
|
3012
|
-
}
|
|
3013
|
-
},
|
|
3014
|
-
trackConversion: (conversionName, value, additionalData) => {
|
|
3015
|
-
if (!keakInstance) {
|
|
3016
|
-
console.warn('Keak SDK not initialized. Call keak.init() first.');
|
|
3017
|
-
return;
|
|
3018
|
-
}
|
|
3019
|
-
keakInstance.trackConversion(conversionName, value, additionalData);
|
|
3020
|
-
},
|
|
3021
|
-
// Live experiment management functions
|
|
2576
|
+
// Live experiment management functions (for toolbar)
|
|
3022
2577
|
getAllLiveExperiments: () => {
|
|
3023
2578
|
if (!keakInstance) {
|
|
3024
2579
|
console.warn('Keak SDK not initialized. Call keak.init() first.');
|
|
@@ -3029,24 +2584,6 @@ const keak = {
|
|
|
3029
2584
|
setExperimentVariant: (experimentId, variant) => {
|
|
3030
2585
|
keakInstance?.setExperimentVariant(experimentId, variant);
|
|
3031
2586
|
},
|
|
3032
|
-
// Impression tracking methods
|
|
3033
|
-
trackImpression: (experimentId, variantName) => {
|
|
3034
|
-
keakInstance?.trackImpression(experimentId, variantName);
|
|
3035
|
-
},
|
|
3036
|
-
getImpressions: (experimentId) => {
|
|
3037
|
-
if (!keakInstance) {
|
|
3038
|
-
console.warn('Keak SDK not initialized. Call keak.init() first.');
|
|
3039
|
-
return {};
|
|
3040
|
-
}
|
|
3041
|
-
return keakInstance.getImpressions(experimentId);
|
|
3042
|
-
},
|
|
3043
|
-
getAllImpressions: () => {
|
|
3044
|
-
if (!keakInstance) {
|
|
3045
|
-
console.warn('Keak SDK not initialized. Call keak.init() first.');
|
|
3046
|
-
return {};
|
|
3047
|
-
}
|
|
3048
|
-
return keakInstance.getAllImpressions();
|
|
3049
|
-
},
|
|
3050
2587
|
};
|
|
3051
2588
|
// Initialize toolbar (called by useKeak hook)
|
|
3052
2589
|
function initializeToolbar(config) {
|