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