@stacksjs/ts-analytics 0.1.6
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/CHANGELOG.md +68 -0
- package/LICENSE.md +21 -0
- package/README.md +361 -0
- package/bin/cli.ts +169 -0
- package/dist/Analytics.d.ts +558 -0
- package/dist/api.d.ts +109 -0
- package/dist/batching.d.ts +93 -0
- package/dist/chunk-2mx7fq49.js +4 -0
- package/dist/chunk-3z29508k.js +204 -0
- package/dist/chunk-deephkz6.js +56 -0
- package/dist/chunk-j261vgyp.js +305 -0
- package/dist/chunk-xga17tz7.js +207 -0
- package/dist/config.d.ts +132 -0
- package/dist/dashboard/components/index.d.ts +80 -0
- package/dist/dashboard/composables/useAnalytics.d.ts +99 -0
- package/dist/dashboard/index.d.ts +110 -0
- package/dist/dashboard/types/index.d.ts +144 -0
- package/dist/dashboard/utils/index.d.ts +81 -0
- package/dist/dynamodb.d.ts +86 -0
- package/dist/funnels.d.ts +104 -0
- package/dist/geolocation.d.ts +96 -0
- package/dist/index.d.ts +371 -0
- package/dist/index.js +10211 -0
- package/dist/infrastructure/cdk.d.ts +60 -0
- package/dist/infrastructure/cloudformation.d.ts +44 -0
- package/dist/infrastructure/index.d.ts +45 -0
- package/dist/infrastructure/setup.d.ts +133 -0
- package/dist/integrations/cloudflare.d.ts +76 -0
- package/dist/integrations/hono.d.ts +60 -0
- package/dist/integrations/index.d.ts +24 -0
- package/dist/integrations/nuxt.d.ts +7 -0
- package/dist/integrations/nuxt.js +42 -0
- package/dist/integrations/runtime/use-ts-analytics.d.ts +18 -0
- package/dist/integrations/runtime/use-ts-analytics.js +17 -0
- package/dist/integrations/stx.d.ts +88 -0
- package/dist/integrations/stx.js +18 -0
- package/dist/lib/crypto-random.d.ts +4 -0
- package/dist/lib/salt.d.ts +16 -0
- package/dist/local.d.ts +56 -0
- package/dist/model-connector.d.ts +145 -0
- package/dist/models/AggregatedStats.d.ts +9 -0
- package/dist/models/CampaignStats.d.ts +9 -0
- package/dist/models/Conversion.d.ts +11 -0
- package/dist/models/CustomEvent.d.ts +11 -0
- package/dist/models/DeviceStats.d.ts +9 -0
- package/dist/models/EventStats.d.ts +9 -0
- package/dist/models/GeoStats.d.ts +9 -0
- package/dist/models/Goal.d.ts +9 -0
- package/dist/models/GoalStats.d.ts +9 -0
- package/dist/models/PageStats.d.ts +11 -0
- package/dist/models/PageView.d.ts +13 -0
- package/dist/models/RealtimeStats.d.ts +9 -0
- package/dist/models/ReferrerStats.d.ts +9 -0
- package/dist/models/Session.d.ts +11 -0
- package/dist/models/Site.d.ts +11 -0
- package/dist/models/index.d.ts +28 -0
- package/dist/models/types.d.ts +60 -0
- package/dist/sqs-buffering.d.ts +243 -0
- package/dist/stacks-integration.d.ts +159 -0
- package/dist/tracking-script.d.ts +71 -0
- package/dist/tracking.d.ts +17 -0
- package/dist/tracking.js +26 -0
- package/dist/types.d.ts +595 -0
- package/dist/utils/geolocation.d.ts +111 -0
- package/dist/utils/user-agent.d.ts +17 -0
- package/dist/version.d.ts +7 -0
- package/package.json +119 -0
- package/src/Analytics.ts +3349 -0
- package/src/api.ts +1286 -0
- package/src/assets/crosswind.css +2220 -0
- package/src/batching.ts +452 -0
- package/src/components/dashboard/index.ts +18 -0
- package/src/config.ts +456 -0
- package/src/dashboard/Dashboard.stx +1517 -0
- package/src/dashboard/components/AlertCard.stx +177 -0
- package/src/dashboard/components/AnalyticsDashboard.stx +354 -0
- package/src/dashboard/components/AnimatedNumber.stx +86 -0
- package/src/dashboard/components/BarChart.stx +220 -0
- package/src/dashboard/components/BrowserBreakdown.stx +98 -0
- package/src/dashboard/components/BrowsersTable.stx +125 -0
- package/src/dashboard/components/CampaignBreakdown.stx +163 -0
- package/src/dashboard/components/CampaignTable.stx +238 -0
- package/src/dashboard/components/CountryList.stx +101 -0
- package/src/dashboard/components/DataTable.stx +226 -0
- package/src/dashboard/components/DateRangePicker.stx +77 -0
- package/src/dashboard/components/DeviceBreakdown.stx +94 -0
- package/src/dashboard/components/DevicesTable.stx +163 -0
- package/src/dashboard/components/DonutChart.stories.ts +55 -0
- package/src/dashboard/components/DonutChart.stx +157 -0
- package/src/dashboard/components/EmptyState.stx +90 -0
- package/src/dashboard/components/EngagementMetrics.stx +183 -0
- package/src/dashboard/components/EventsSection.stx +192 -0
- package/src/dashboard/components/FilterBar.stx +104 -0
- package/src/dashboard/components/FullAnalyticsDashboard.stx +455 -0
- package/src/dashboard/components/FunnelChart.stx +142 -0
- package/src/dashboard/components/GeoTable.stx +337 -0
- package/src/dashboard/components/GoalsPanel.stx +109 -0
- package/src/dashboard/components/Header.stx +39 -0
- package/src/dashboard/components/HeatmapChart.stx +140 -0
- package/src/dashboard/components/LiveActivityFeed.stx +172 -0
- package/src/dashboard/components/MetricComparison.stx +106 -0
- package/src/dashboard/components/MiniStats.stx +96 -0
- package/src/dashboard/components/OSBreakdown.stx +124 -0
- package/src/dashboard/components/PageDetailCard.stx +102 -0
- package/src/dashboard/components/PagesTable.stx +127 -0
- package/src/dashboard/components/ProgressRing.stx +89 -0
- package/src/dashboard/components/RealtimeCounter.stories.ts +45 -0
- package/src/dashboard/components/RealtimeCounter.stx +46 -0
- package/src/dashboard/components/ReferrersTable.stx +180 -0
- package/src/dashboard/components/SparklineChart.stx +160 -0
- package/src/dashboard/components/StatCard.stories.ts +58 -0
- package/src/dashboard/components/StatCard.stx +90 -0
- package/src/dashboard/components/SummaryStats.stx +81 -0
- package/src/dashboard/components/TabNav.stx +66 -0
- package/src/dashboard/components/ThemeSwitcher.stx +124 -0
- package/src/dashboard/components/TimeSeriesChart.stx +106 -0
- package/src/dashboard/components/TopList.stories.ts +58 -0
- package/src/dashboard/components/TopList.stx +74 -0
- package/src/dashboard/components/TrendIndicator.stx +84 -0
- package/src/dashboard/components/heatmap/ClickHeatmap.stx +264 -0
- package/src/dashboard/components/heatmap/ElementClickList.stx +125 -0
- package/src/dashboard/components/heatmap/HeatmapControls.stx +125 -0
- package/src/dashboard/components/heatmap/HeatmapLegend.stx +69 -0
- package/src/dashboard/components/heatmap/PageHeatmap.stx +264 -0
- package/src/dashboard/components/heatmap/ScrollHeatmap.stx +127 -0
- package/src/dashboard/components/heatmap/index.ts +12 -0
- package/src/dashboard/components/index.ts +86 -0
- package/src/dashboard/composables/useAnalytics.ts +465 -0
- package/src/dashboard/demo/DemoApp.stx +370 -0
- package/src/dashboard/demo/index.ts +8 -0
- package/src/dashboard/demo/mockData.ts +234 -0
- package/src/dashboard/index.ts +117 -0
- package/src/dashboard/stx-shim.d.ts +4 -0
- package/src/dashboard/types/index.ts +203 -0
- package/src/dashboard/utils/index.ts +426 -0
- package/src/dynamodb.ts +344 -0
- package/src/funnels.ts +534 -0
- package/src/geolocation.ts +515 -0
- package/src/handlers/alerts.ts +328 -0
- package/src/handlers/annotations.ts +128 -0
- package/src/handlers/api-keys.ts +240 -0
- package/src/handlers/auth.ts +1020 -0
- package/src/handlers/authz.ts +137 -0
- package/src/handlers/collect.ts +724 -0
- package/src/handlers/data.ts +625 -0
- package/src/handlers/experiments.ts +138 -0
- package/src/handlers/funnels.ts +216 -0
- package/src/handlers/goals.ts +218 -0
- package/src/handlers/heatmaps.ts +272 -0
- package/src/handlers/index.ts +56 -0
- package/src/handlers/lib/read-cache.ts +63 -0
- package/src/handlers/misc.ts +486 -0
- package/src/handlers/oauth.ts +233 -0
- package/src/handlers/performance.ts +388 -0
- package/src/handlers/sessions.ts +413 -0
- package/src/handlers/sharing.ts +198 -0
- package/src/handlers/stats.ts +1368 -0
- package/src/handlers/team.ts +161 -0
- package/src/handlers/uptime.ts +283 -0
- package/src/handlers/views.ts +390 -0
- package/src/handlers/webhooks.ts +226 -0
- package/src/heatmap/index.ts +32 -0
- package/src/heatmap/tracking-script.ts +452 -0
- package/src/heatmap/types.ts +79 -0
- package/src/index.ts +387 -0
- package/src/infrastructure/cdk.ts +496 -0
- package/src/infrastructure/cloudformation.ts +595 -0
- package/src/infrastructure/index.ts +48 -0
- package/src/infrastructure/setup.ts +611 -0
- package/src/integrations/cloudflare.ts +732 -0
- package/src/integrations/hono.ts +589 -0
- package/src/integrations/index.ts +27 -0
- package/src/integrations/nuxt.ts +78 -0
- package/src/integrations/runtime/use-ts-analytics.ts +32 -0
- package/src/integrations/stx.ts +138 -0
- package/src/jobs/index.ts +127 -0
- package/src/lib/crypto-random.ts +41 -0
- package/src/lib/ddb-errors.ts +20 -0
- package/src/lib/dynamodb.ts +216 -0
- package/src/lib/email.ts +38 -0
- package/src/lib/ga-import.ts +471 -0
- package/src/lib/ga4-api.ts +244 -0
- package/src/lib/goals.ts +205 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/ingest-counters.ts +123 -0
- package/src/lib/log.ts +36 -0
- package/src/lib/membership.ts +98 -0
- package/src/lib/plans.ts +90 -0
- package/src/lib/quota.ts +46 -0
- package/src/lib/rate-limit.ts +27 -0
- package/src/lib/rollups.ts +472 -0
- package/src/lib/salt.ts +97 -0
- package/src/lib/scheduler.ts +97 -0
- package/src/lib/significance.ts +66 -0
- package/src/lib/site-retention.ts +56 -0
- package/src/local.ts +360 -0
- package/src/model-connector.ts +616 -0
- package/src/models/AggregatedStats.ts +162 -0
- package/src/models/CampaignStats.ts +141 -0
- package/src/models/Conversion.ts +135 -0
- package/src/models/CustomEvent.ts +123 -0
- package/src/models/DeviceStats.ts +105 -0
- package/src/models/EventStats.ts +108 -0
- package/src/models/GeoStats.ts +112 -0
- package/src/models/Goal.ts +105 -0
- package/src/models/GoalStats.ts +106 -0
- package/src/models/PageStats.ts +152 -0
- package/src/models/PageView.ts +277 -0
- package/src/models/RealtimeStats.ts +96 -0
- package/src/models/ReferrerStats.ts +115 -0
- package/src/models/Session.ts +235 -0
- package/src/models/Site.ts +115 -0
- package/src/models/index.ts +50 -0
- package/src/models/orm/index.ts +2043 -0
- package/src/models/types.ts +71 -0
- package/src/router.ts +521 -0
- package/src/sqs-buffering.ts +806 -0
- package/src/stacks-integration.ts +527 -0
- package/src/tracking-script.ts +944 -0
- package/src/tracking.ts +27 -0
- package/src/types/analytics.ts +219 -0
- package/src/types/api.ts +75 -0
- package/src/types/bun-router.d.ts +15 -0
- package/src/types/dashboard.ts +62 -0
- package/src/types/index.ts +66 -0
- package/src/types/stx.d.ts +27 -0
- package/src/types/window.d.ts +101 -0
- package/src/types.ts +911 -0
- package/src/utils/cache.ts +148 -0
- package/src/utils/date.ts +118 -0
- package/src/utils/filters.ts +71 -0
- package/src/utils/geolocation.ts +323 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/response.ts +180 -0
- package/src/utils/timezone-country.ts +242 -0
- package/src/utils/user-agent.ts +110 -0
- package/src/version.ts +7 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/* eslint-disable prefer-const */
|
|
2
|
+
/**
|
|
3
|
+
* Heatmap Tracking Script Generator
|
|
4
|
+
*
|
|
5
|
+
* Generates client-side JavaScript for tracking clicks, mouse movements,
|
|
6
|
+
* and scroll positions for heatmap visualization.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { HeatmapConfig } from './types'
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Main Generator
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Generate the heatmap tracking code
|
|
17
|
+
*/
|
|
18
|
+
export function buildHeatmapTracking(config: HeatmapConfig): string {
|
|
19
|
+
const parts: string[] = []
|
|
20
|
+
|
|
21
|
+
// Configuration
|
|
22
|
+
parts.push(buildHeatmapConfig(config))
|
|
23
|
+
parts.push('')
|
|
24
|
+
|
|
25
|
+
// CSS selector generator
|
|
26
|
+
parts.push(buildSelectorGenerator(config.selectorDepth ?? 5))
|
|
27
|
+
parts.push('')
|
|
28
|
+
|
|
29
|
+
// Movement buffer
|
|
30
|
+
if (config.trackMovements) {
|
|
31
|
+
parts.push(buildMovementBuffer(config))
|
|
32
|
+
parts.push('')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Scroll tracking buffer
|
|
36
|
+
if (config.trackScrollPositions) {
|
|
37
|
+
parts.push(buildScrollBuffer())
|
|
38
|
+
parts.push('')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Click tracking
|
|
42
|
+
if (config.trackClicks !== false) {
|
|
43
|
+
parts.push(buildClickTracking())
|
|
44
|
+
parts.push('')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Movement tracking
|
|
48
|
+
if (config.trackMovements) {
|
|
49
|
+
parts.push(buildMovementTracking(config))
|
|
50
|
+
parts.push('')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Scroll position tracking
|
|
54
|
+
if (config.trackScrollPositions) {
|
|
55
|
+
parts.push(buildScrollPositionTracking())
|
|
56
|
+
parts.push('')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Flush on unload
|
|
60
|
+
parts.push(buildFlushOnUnload(config))
|
|
61
|
+
parts.push('')
|
|
62
|
+
|
|
63
|
+
// Initialization
|
|
64
|
+
parts.push(buildHeatmapInit(config))
|
|
65
|
+
|
|
66
|
+
return parts.join('\n')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Section Builders
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
function buildHeatmapConfig(config: HeatmapConfig): string {
|
|
74
|
+
return `// Heatmap Configuration
|
|
75
|
+
var HEATMAP_CONFIG = {
|
|
76
|
+
trackClicks: ${config.trackClicks !== false},
|
|
77
|
+
trackMovements: ${config.trackMovements ?? false},
|
|
78
|
+
trackScrollPositions: ${config.trackScrollPositions ?? false},
|
|
79
|
+
movementSampleInterval: ${config.movementSampleInterval ?? 100},
|
|
80
|
+
maxMovementPoints: ${config.maxMovementPoints ?? 500},
|
|
81
|
+
flushInterval: ${config.flushInterval ?? 5000},
|
|
82
|
+
debug: ${config.debug ?? false}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
function hmLog() {
|
|
86
|
+
if (HEATMAP_CONFIG.debug && console && console.log) {
|
|
87
|
+
console.log.apply(console, ['[Heatmap]'].concat(Array.prototype.slice.call(arguments)));
|
|
88
|
+
}
|
|
89
|
+
}`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildSelectorGenerator(maxDepth: number): string {
|
|
93
|
+
return `// CSS Selector Generator
|
|
94
|
+
function getSelector(el, maxDepth) {
|
|
95
|
+
maxDepth = maxDepth || ${maxDepth};
|
|
96
|
+
var path = [];
|
|
97
|
+
var depth = 0;
|
|
98
|
+
|
|
99
|
+
while (el && el.nodeType === 1 && depth < maxDepth) {
|
|
100
|
+
var selector = el.nodeName.toLowerCase();
|
|
101
|
+
|
|
102
|
+
// Use ID if available (most specific)
|
|
103
|
+
if (el.id) {
|
|
104
|
+
path.unshift('#' + el.id);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Use data-testid or data-analytics-id if available
|
|
109
|
+
var testId = el.getAttribute('data-testid') || el.getAttribute('data-analytics-id');
|
|
110
|
+
if (testId) {
|
|
111
|
+
path.unshift('[data-testid="' + testId + '"]');
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Use classes if available (limited)
|
|
116
|
+
var classes = el.className;
|
|
117
|
+
if (typeof classes === 'string' && classes.trim()) {
|
|
118
|
+
var classList = classes.trim().split(/\\s+/).slice(0, 2);
|
|
119
|
+
selector += '.' + classList.join('.');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Add nth-child for specificity
|
|
123
|
+
var parent = el.parentNode;
|
|
124
|
+
if (parent) {
|
|
125
|
+
var siblings = parent.children;
|
|
126
|
+
var index = 1;
|
|
127
|
+
for (var i = 0; i < siblings.length; i++) {
|
|
128
|
+
if (siblings[i] === el) break;
|
|
129
|
+
if (siblings[i].nodeName === el.nodeName) index++;
|
|
130
|
+
}
|
|
131
|
+
if (siblings.length > 1) {
|
|
132
|
+
selector += ':nth-of-type(' + index + ')';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
path.unshift(selector);
|
|
137
|
+
el = el.parentNode;
|
|
138
|
+
depth++;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return path.join(' > ');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getDataAttributes(el) {
|
|
145
|
+
var attrs = {};
|
|
146
|
+
if (el.dataset) {
|
|
147
|
+
for (var key in el.dataset) {
|
|
148
|
+
if (el.dataset.hasOwnProperty(key)) {
|
|
149
|
+
attrs[key] = el.dataset[key];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return Object.keys(attrs).length > 0 ? attrs : null;
|
|
154
|
+
}`
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function buildMovementBuffer(config: HeatmapConfig): string {
|
|
158
|
+
return `// Movement Buffer
|
|
159
|
+
var movementBuffer = [];
|
|
160
|
+
var lastMovementFlush = Date.now();
|
|
161
|
+
var movementFlushTimer = null;
|
|
162
|
+
|
|
163
|
+
function flushMovements() {
|
|
164
|
+
if (movementBuffer.length === 0) return;
|
|
165
|
+
|
|
166
|
+
var batch = {
|
|
167
|
+
points: movementBuffer.slice(),
|
|
168
|
+
vw: window.innerWidth,
|
|
169
|
+
vh: window.innerHeight
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
movementBuffer = [];
|
|
173
|
+
lastMovementFlush = Date.now();
|
|
174
|
+
|
|
175
|
+
sendBeacon({
|
|
176
|
+
s: CONFIG.siteId,
|
|
177
|
+
sid: session.id,
|
|
178
|
+
e: 'hm_move',
|
|
179
|
+
u: window.location.pathname,
|
|
180
|
+
p: batch,
|
|
181
|
+
ts: Date.now()
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
hmLog('Flushed', batch.points.length, 'movement points');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function scheduleMovementFlush() {
|
|
188
|
+
if (movementFlushTimer) return;
|
|
189
|
+
movementFlushTimer = setTimeout(function() {
|
|
190
|
+
movementFlushTimer = null;
|
|
191
|
+
flushMovements();
|
|
192
|
+
}, ${config.flushInterval ?? 5000});
|
|
193
|
+
}`
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function buildScrollBuffer(): string {
|
|
197
|
+
return `// Scroll Buffer
|
|
198
|
+
var scrollDepthTimes = {};
|
|
199
|
+
var maxScrollDepth = 0;
|
|
200
|
+
var scrollStartTime = Date.now();
|
|
201
|
+
var lastScrollDepth = 0;
|
|
202
|
+
|
|
203
|
+
function updateScrollDepth() {
|
|
204
|
+
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
205
|
+
var docHeight = Math.max(
|
|
206
|
+
document.body.scrollHeight,
|
|
207
|
+
document.documentElement.scrollHeight
|
|
208
|
+
) - window.innerHeight;
|
|
209
|
+
|
|
210
|
+
if (docHeight <= 0) return;
|
|
211
|
+
|
|
212
|
+
var depth = Math.min(100, Math.round((scrollTop / docHeight) * 100));
|
|
213
|
+
|
|
214
|
+
// Track time at each 10% increment
|
|
215
|
+
var depthBucket = Math.floor(depth / 10) * 10;
|
|
216
|
+
|
|
217
|
+
if (depthBucket > 0) {
|
|
218
|
+
var now = Date.now();
|
|
219
|
+
var timeAtPrevDepth = now - scrollStartTime;
|
|
220
|
+
|
|
221
|
+
// Update time spent at previous depth bucket
|
|
222
|
+
if (lastScrollDepth > 0) {
|
|
223
|
+
var prevBucket = Math.floor(lastScrollDepth / 10) * 10;
|
|
224
|
+
scrollDepthTimes[prevBucket] = (scrollDepthTimes[prevBucket] || 0) + timeAtPrevDepth;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
scrollStartTime = now;
|
|
228
|
+
lastScrollDepth = depth;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (depth > maxScrollDepth) {
|
|
232
|
+
maxScrollDepth = depth;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function getScrollData() {
|
|
237
|
+
// Final update
|
|
238
|
+
if (lastScrollDepth > 0) {
|
|
239
|
+
var prevBucket = Math.floor(lastScrollDepth / 10) * 10;
|
|
240
|
+
scrollDepthTimes[prevBucket] = (scrollDepthTimes[prevBucket] || 0) + (Date.now() - scrollStartTime);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
depths: scrollDepthTimes,
|
|
245
|
+
maxDepth: maxScrollDepth,
|
|
246
|
+
maxY: window.pageYOffset || document.documentElement.scrollTop,
|
|
247
|
+
docHeight: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight),
|
|
248
|
+
vh: window.innerHeight
|
|
249
|
+
};
|
|
250
|
+
}`
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function buildClickTracking(): string {
|
|
254
|
+
return `// Click Tracking
|
|
255
|
+
function trackClick(event) {
|
|
256
|
+
var el = event.target;
|
|
257
|
+
if (!el || el.nodeType !== 1) return;
|
|
258
|
+
|
|
259
|
+
// Get coordinates
|
|
260
|
+
var rect = document.documentElement.getBoundingClientRect();
|
|
261
|
+
var viewportX = event.clientX;
|
|
262
|
+
var viewportY = event.clientY;
|
|
263
|
+
var documentX = event.pageX;
|
|
264
|
+
var documentY = event.pageY;
|
|
265
|
+
|
|
266
|
+
// Get element info
|
|
267
|
+
var selector = getSelector(el);
|
|
268
|
+
var tag = el.tagName.toLowerCase();
|
|
269
|
+
var text = (el.textContent || el.innerText || '').trim().substring(0, 50);
|
|
270
|
+
var dataAttrs = getDataAttributes(el);
|
|
271
|
+
|
|
272
|
+
var clickData = {
|
|
273
|
+
vx: viewportX,
|
|
274
|
+
vy: viewportY,
|
|
275
|
+
dx: documentX,
|
|
276
|
+
dy: documentY,
|
|
277
|
+
selector: selector,
|
|
278
|
+
tag: tag,
|
|
279
|
+
vw: window.innerWidth,
|
|
280
|
+
vh: window.innerHeight
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
if (text) clickData.text = text;
|
|
284
|
+
if (dataAttrs) clickData.dataAttrs = dataAttrs;
|
|
285
|
+
|
|
286
|
+
sendBeacon({
|
|
287
|
+
s: CONFIG.siteId,
|
|
288
|
+
sid: session.id,
|
|
289
|
+
e: 'hm_click',
|
|
290
|
+
u: window.location.pathname,
|
|
291
|
+
p: clickData,
|
|
292
|
+
ts: Date.now()
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
hmLog('Click tracked:', selector, 'at', viewportX, viewportY);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
document.addEventListener('click', trackClick, true);`
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function buildMovementTracking(_config: HeatmapConfig): string {
|
|
302
|
+
return `// Movement Tracking
|
|
303
|
+
var lastMovementTime = 0;
|
|
304
|
+
var rafId = null;
|
|
305
|
+
|
|
306
|
+
function trackMovement(event) {
|
|
307
|
+
var now = Date.now();
|
|
308
|
+
if (now - lastMovementTime < HEATMAP_CONFIG.movementSampleInterval) return;
|
|
309
|
+
lastMovementTime = now;
|
|
310
|
+
|
|
311
|
+
// Normalize to percentage of viewport
|
|
312
|
+
var x = Math.round((event.clientX / window.innerWidth) * 100);
|
|
313
|
+
var y = Math.round((event.clientY / window.innerHeight) * 100);
|
|
314
|
+
|
|
315
|
+
movementBuffer.push([x, y, now]);
|
|
316
|
+
|
|
317
|
+
// Flush if buffer is full
|
|
318
|
+
if (movementBuffer.length >= HEATMAP_CONFIG.maxMovementPoints) {
|
|
319
|
+
flushMovements();
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
scheduleMovementFlush();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
document.addEventListener('mousemove', function(event) {
|
|
327
|
+
if (rafId) return;
|
|
328
|
+
rafId = requestAnimationFrame(function() {
|
|
329
|
+
rafId = null;
|
|
330
|
+
trackMovement(event);
|
|
331
|
+
});
|
|
332
|
+
}, { passive: true });`
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function buildScrollPositionTracking(): string {
|
|
336
|
+
return `// Scroll Position Tracking
|
|
337
|
+
var scrollTrackThrottle = null;
|
|
338
|
+
|
|
339
|
+
window.addEventListener('scroll', function() {
|
|
340
|
+
if (scrollTrackThrottle) return;
|
|
341
|
+
scrollTrackThrottle = setTimeout(function() {
|
|
342
|
+
scrollTrackThrottle = null;
|
|
343
|
+
updateScrollDepth();
|
|
344
|
+
}, 100);
|
|
345
|
+
}, { passive: true });`
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function buildFlushOnUnload(config: HeatmapConfig): string {
|
|
349
|
+
const parts: string[] = [
|
|
350
|
+
`// Flush on Unload
|
|
351
|
+
function flushAllHeatmapData() {`,
|
|
352
|
+
]
|
|
353
|
+
|
|
354
|
+
if (config.trackMovements) {
|
|
355
|
+
parts.push(` if (movementBuffer.length > 0) {
|
|
356
|
+
flushMovements();
|
|
357
|
+
}`)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (config.trackScrollPositions) {
|
|
361
|
+
parts.push(`
|
|
362
|
+
var scrollData = getScrollData();
|
|
363
|
+
if (scrollData.maxDepth > 0) {
|
|
364
|
+
sendBeacon({
|
|
365
|
+
s: CONFIG.siteId,
|
|
366
|
+
sid: session.id,
|
|
367
|
+
e: 'hm_scroll',
|
|
368
|
+
u: window.location.pathname,
|
|
369
|
+
p: scrollData,
|
|
370
|
+
ts: Date.now()
|
|
371
|
+
});
|
|
372
|
+
hmLog('Scroll data flushed:', scrollData);
|
|
373
|
+
}`)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
parts.push(`}
|
|
377
|
+
|
|
378
|
+
window.addEventListener('beforeunload', flushAllHeatmapData);
|
|
379
|
+
window.addEventListener('pagehide', flushAllHeatmapData);
|
|
380
|
+
|
|
381
|
+
// Also flush periodically
|
|
382
|
+
setInterval(function() {
|
|
383
|
+
if (movementBuffer && movementBuffer.length > 0) {
|
|
384
|
+
flushMovements();
|
|
385
|
+
}
|
|
386
|
+
}, ${config.flushInterval ?? 5000});`)
|
|
387
|
+
|
|
388
|
+
return parts.join('\n')
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function buildHeatmapInit(_config: HeatmapConfig): string {
|
|
392
|
+
return `// Heatmap Initialization
|
|
393
|
+
hmLog('Heatmap tracking initialized');
|
|
394
|
+
hmLog('Click tracking:', HEATMAP_CONFIG.trackClicks);
|
|
395
|
+
hmLog('Movement tracking:', HEATMAP_CONFIG.trackMovements);
|
|
396
|
+
hmLog('Scroll tracking:', HEATMAP_CONFIG.trackScrollPositions);`
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// Integration
|
|
401
|
+
// ============================================================================
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Generate a standalone heatmap tracking script
|
|
405
|
+
*/
|
|
406
|
+
export function generateHeatmapScript(
|
|
407
|
+
siteId: string,
|
|
408
|
+
apiEndpoint: string,
|
|
409
|
+
config: HeatmapConfig = {},
|
|
410
|
+
): string {
|
|
411
|
+
const fullConfig: HeatmapConfig = {
|
|
412
|
+
trackClicks: true,
|
|
413
|
+
trackMovements: false,
|
|
414
|
+
trackScrollPositions: true,
|
|
415
|
+
...config,
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return `(function(window, document) {
|
|
419
|
+
"use strict";
|
|
420
|
+
|
|
421
|
+
// Base configuration (assumes main tracking script is loaded)
|
|
422
|
+
if (typeof CONFIG === 'undefined') {
|
|
423
|
+
window.CONFIG = {
|
|
424
|
+
siteId: '${siteId}',
|
|
425
|
+
endpoint: '${apiEndpoint}/collect',
|
|
426
|
+
debug: ${config.debug ?? false}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (typeof session === 'undefined') {
|
|
431
|
+
window.session = { id: 'hm-' + Date.now() };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (typeof sendBeacon === 'undefined') {
|
|
435
|
+
window.sendBeacon = function(data) {
|
|
436
|
+
var payload = JSON.stringify(data);
|
|
437
|
+
if (navigator.sendBeacon) {
|
|
438
|
+
navigator.sendBeacon(CONFIG.endpoint, payload);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
var xhr = new XMLHttpRequest();
|
|
442
|
+
xhr.open('POST', CONFIG.endpoint, true);
|
|
443
|
+
xhr.setRequestHeader('Content-Type', 'application/json');
|
|
444
|
+
xhr.send(payload);
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
${buildHeatmapTracking(fullConfig)}
|
|
450
|
+
|
|
451
|
+
})(window, document);`
|
|
452
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heatmap Tracking Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for heatmap tracking
|
|
7
|
+
*/
|
|
8
|
+
export interface HeatmapConfig {
|
|
9
|
+
/** Enable click tracking */
|
|
10
|
+
trackClicks?: boolean
|
|
11
|
+
/** Enable mouse movement tracking */
|
|
12
|
+
trackMovements?: boolean
|
|
13
|
+
/** Movement sampling interval in ms (default: 100) */
|
|
14
|
+
movementSampleInterval?: number
|
|
15
|
+
/** Maximum movement points per page before flush (default: 500) */
|
|
16
|
+
maxMovementPoints?: number
|
|
17
|
+
/** Enable scroll position tracking */
|
|
18
|
+
trackScrollPositions?: boolean
|
|
19
|
+
/** Flush interval for batched data in ms (default: 5000) */
|
|
20
|
+
flushInterval?: number
|
|
21
|
+
/** Maximum CSS selector depth (default: 5) */
|
|
22
|
+
selectorDepth?: number
|
|
23
|
+
/** Debug mode */
|
|
24
|
+
debug?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Click event data sent to the API
|
|
29
|
+
*/
|
|
30
|
+
export interface HeatmapClickEvent {
|
|
31
|
+
/** Viewport X coordinate */
|
|
32
|
+
vx: number
|
|
33
|
+
/** Viewport Y coordinate */
|
|
34
|
+
vy: number
|
|
35
|
+
/** Document X coordinate */
|
|
36
|
+
dx: number
|
|
37
|
+
/** Document Y coordinate */
|
|
38
|
+
dy: number
|
|
39
|
+
/** CSS selector path to element */
|
|
40
|
+
selector: string
|
|
41
|
+
/** Element tag name */
|
|
42
|
+
tag: string
|
|
43
|
+
/** Element text content (truncated) */
|
|
44
|
+
text?: string
|
|
45
|
+
/** data-* attributes */
|
|
46
|
+
dataAttrs?: Record<string, string>
|
|
47
|
+
/** Viewport width */
|
|
48
|
+
vw: number
|
|
49
|
+
/** Viewport height */
|
|
50
|
+
vh: number
|
|
51
|
+
/** Timestamp */
|
|
52
|
+
ts: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Movement batch data sent to the API
|
|
57
|
+
*/
|
|
58
|
+
export interface HeatmapMovementBatch {
|
|
59
|
+
/** Array of [x, y, timestamp] tuples */
|
|
60
|
+
points: Array<[number, number, number]>
|
|
61
|
+
/** Viewport width */
|
|
62
|
+
vw: number
|
|
63
|
+
/** Viewport height */
|
|
64
|
+
vh: number
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Scroll position data sent to the API
|
|
69
|
+
*/
|
|
70
|
+
export interface HeatmapScrollData {
|
|
71
|
+
/** Scroll depth percentages reached with time spent */
|
|
72
|
+
depths: Record<number, number>
|
|
73
|
+
/** Maximum scroll depth reached (0-100) */
|
|
74
|
+
maxDepth: number
|
|
75
|
+
/** Maximum scroll position in pixels */
|
|
76
|
+
maxY: number
|
|
77
|
+
/** Total document height */
|
|
78
|
+
docHeight: number
|
|
79
|
+
}
|