@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.
Files changed (237) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +361 -0
  4. package/bin/cli.ts +169 -0
  5. package/dist/Analytics.d.ts +558 -0
  6. package/dist/api.d.ts +109 -0
  7. package/dist/batching.d.ts +93 -0
  8. package/dist/chunk-2mx7fq49.js +4 -0
  9. package/dist/chunk-3z29508k.js +204 -0
  10. package/dist/chunk-deephkz6.js +56 -0
  11. package/dist/chunk-j261vgyp.js +305 -0
  12. package/dist/chunk-xga17tz7.js +207 -0
  13. package/dist/config.d.ts +132 -0
  14. package/dist/dashboard/components/index.d.ts +80 -0
  15. package/dist/dashboard/composables/useAnalytics.d.ts +99 -0
  16. package/dist/dashboard/index.d.ts +110 -0
  17. package/dist/dashboard/types/index.d.ts +144 -0
  18. package/dist/dashboard/utils/index.d.ts +81 -0
  19. package/dist/dynamodb.d.ts +86 -0
  20. package/dist/funnels.d.ts +104 -0
  21. package/dist/geolocation.d.ts +96 -0
  22. package/dist/index.d.ts +371 -0
  23. package/dist/index.js +10211 -0
  24. package/dist/infrastructure/cdk.d.ts +60 -0
  25. package/dist/infrastructure/cloudformation.d.ts +44 -0
  26. package/dist/infrastructure/index.d.ts +45 -0
  27. package/dist/infrastructure/setup.d.ts +133 -0
  28. package/dist/integrations/cloudflare.d.ts +76 -0
  29. package/dist/integrations/hono.d.ts +60 -0
  30. package/dist/integrations/index.d.ts +24 -0
  31. package/dist/integrations/nuxt.d.ts +7 -0
  32. package/dist/integrations/nuxt.js +42 -0
  33. package/dist/integrations/runtime/use-ts-analytics.d.ts +18 -0
  34. package/dist/integrations/runtime/use-ts-analytics.js +17 -0
  35. package/dist/integrations/stx.d.ts +88 -0
  36. package/dist/integrations/stx.js +18 -0
  37. package/dist/lib/crypto-random.d.ts +4 -0
  38. package/dist/lib/salt.d.ts +16 -0
  39. package/dist/local.d.ts +56 -0
  40. package/dist/model-connector.d.ts +145 -0
  41. package/dist/models/AggregatedStats.d.ts +9 -0
  42. package/dist/models/CampaignStats.d.ts +9 -0
  43. package/dist/models/Conversion.d.ts +11 -0
  44. package/dist/models/CustomEvent.d.ts +11 -0
  45. package/dist/models/DeviceStats.d.ts +9 -0
  46. package/dist/models/EventStats.d.ts +9 -0
  47. package/dist/models/GeoStats.d.ts +9 -0
  48. package/dist/models/Goal.d.ts +9 -0
  49. package/dist/models/GoalStats.d.ts +9 -0
  50. package/dist/models/PageStats.d.ts +11 -0
  51. package/dist/models/PageView.d.ts +13 -0
  52. package/dist/models/RealtimeStats.d.ts +9 -0
  53. package/dist/models/ReferrerStats.d.ts +9 -0
  54. package/dist/models/Session.d.ts +11 -0
  55. package/dist/models/Site.d.ts +11 -0
  56. package/dist/models/index.d.ts +28 -0
  57. package/dist/models/types.d.ts +60 -0
  58. package/dist/sqs-buffering.d.ts +243 -0
  59. package/dist/stacks-integration.d.ts +159 -0
  60. package/dist/tracking-script.d.ts +71 -0
  61. package/dist/tracking.d.ts +17 -0
  62. package/dist/tracking.js +26 -0
  63. package/dist/types.d.ts +595 -0
  64. package/dist/utils/geolocation.d.ts +111 -0
  65. package/dist/utils/user-agent.d.ts +17 -0
  66. package/dist/version.d.ts +7 -0
  67. package/package.json +119 -0
  68. package/src/Analytics.ts +3349 -0
  69. package/src/api.ts +1286 -0
  70. package/src/assets/crosswind.css +2220 -0
  71. package/src/batching.ts +452 -0
  72. package/src/components/dashboard/index.ts +18 -0
  73. package/src/config.ts +456 -0
  74. package/src/dashboard/Dashboard.stx +1517 -0
  75. package/src/dashboard/components/AlertCard.stx +177 -0
  76. package/src/dashboard/components/AnalyticsDashboard.stx +354 -0
  77. package/src/dashboard/components/AnimatedNumber.stx +86 -0
  78. package/src/dashboard/components/BarChart.stx +220 -0
  79. package/src/dashboard/components/BrowserBreakdown.stx +98 -0
  80. package/src/dashboard/components/BrowsersTable.stx +125 -0
  81. package/src/dashboard/components/CampaignBreakdown.stx +163 -0
  82. package/src/dashboard/components/CampaignTable.stx +238 -0
  83. package/src/dashboard/components/CountryList.stx +101 -0
  84. package/src/dashboard/components/DataTable.stx +226 -0
  85. package/src/dashboard/components/DateRangePicker.stx +77 -0
  86. package/src/dashboard/components/DeviceBreakdown.stx +94 -0
  87. package/src/dashboard/components/DevicesTable.stx +163 -0
  88. package/src/dashboard/components/DonutChart.stories.ts +55 -0
  89. package/src/dashboard/components/DonutChart.stx +157 -0
  90. package/src/dashboard/components/EmptyState.stx +90 -0
  91. package/src/dashboard/components/EngagementMetrics.stx +183 -0
  92. package/src/dashboard/components/EventsSection.stx +192 -0
  93. package/src/dashboard/components/FilterBar.stx +104 -0
  94. package/src/dashboard/components/FullAnalyticsDashboard.stx +455 -0
  95. package/src/dashboard/components/FunnelChart.stx +142 -0
  96. package/src/dashboard/components/GeoTable.stx +337 -0
  97. package/src/dashboard/components/GoalsPanel.stx +109 -0
  98. package/src/dashboard/components/Header.stx +39 -0
  99. package/src/dashboard/components/HeatmapChart.stx +140 -0
  100. package/src/dashboard/components/LiveActivityFeed.stx +172 -0
  101. package/src/dashboard/components/MetricComparison.stx +106 -0
  102. package/src/dashboard/components/MiniStats.stx +96 -0
  103. package/src/dashboard/components/OSBreakdown.stx +124 -0
  104. package/src/dashboard/components/PageDetailCard.stx +102 -0
  105. package/src/dashboard/components/PagesTable.stx +127 -0
  106. package/src/dashboard/components/ProgressRing.stx +89 -0
  107. package/src/dashboard/components/RealtimeCounter.stories.ts +45 -0
  108. package/src/dashboard/components/RealtimeCounter.stx +46 -0
  109. package/src/dashboard/components/ReferrersTable.stx +180 -0
  110. package/src/dashboard/components/SparklineChart.stx +160 -0
  111. package/src/dashboard/components/StatCard.stories.ts +58 -0
  112. package/src/dashboard/components/StatCard.stx +90 -0
  113. package/src/dashboard/components/SummaryStats.stx +81 -0
  114. package/src/dashboard/components/TabNav.stx +66 -0
  115. package/src/dashboard/components/ThemeSwitcher.stx +124 -0
  116. package/src/dashboard/components/TimeSeriesChart.stx +106 -0
  117. package/src/dashboard/components/TopList.stories.ts +58 -0
  118. package/src/dashboard/components/TopList.stx +74 -0
  119. package/src/dashboard/components/TrendIndicator.stx +84 -0
  120. package/src/dashboard/components/heatmap/ClickHeatmap.stx +264 -0
  121. package/src/dashboard/components/heatmap/ElementClickList.stx +125 -0
  122. package/src/dashboard/components/heatmap/HeatmapControls.stx +125 -0
  123. package/src/dashboard/components/heatmap/HeatmapLegend.stx +69 -0
  124. package/src/dashboard/components/heatmap/PageHeatmap.stx +264 -0
  125. package/src/dashboard/components/heatmap/ScrollHeatmap.stx +127 -0
  126. package/src/dashboard/components/heatmap/index.ts +12 -0
  127. package/src/dashboard/components/index.ts +86 -0
  128. package/src/dashboard/composables/useAnalytics.ts +465 -0
  129. package/src/dashboard/demo/DemoApp.stx +370 -0
  130. package/src/dashboard/demo/index.ts +8 -0
  131. package/src/dashboard/demo/mockData.ts +234 -0
  132. package/src/dashboard/index.ts +117 -0
  133. package/src/dashboard/stx-shim.d.ts +4 -0
  134. package/src/dashboard/types/index.ts +203 -0
  135. package/src/dashboard/utils/index.ts +426 -0
  136. package/src/dynamodb.ts +344 -0
  137. package/src/funnels.ts +534 -0
  138. package/src/geolocation.ts +515 -0
  139. package/src/handlers/alerts.ts +328 -0
  140. package/src/handlers/annotations.ts +128 -0
  141. package/src/handlers/api-keys.ts +240 -0
  142. package/src/handlers/auth.ts +1020 -0
  143. package/src/handlers/authz.ts +137 -0
  144. package/src/handlers/collect.ts +724 -0
  145. package/src/handlers/data.ts +625 -0
  146. package/src/handlers/experiments.ts +138 -0
  147. package/src/handlers/funnels.ts +216 -0
  148. package/src/handlers/goals.ts +218 -0
  149. package/src/handlers/heatmaps.ts +272 -0
  150. package/src/handlers/index.ts +56 -0
  151. package/src/handlers/lib/read-cache.ts +63 -0
  152. package/src/handlers/misc.ts +486 -0
  153. package/src/handlers/oauth.ts +233 -0
  154. package/src/handlers/performance.ts +388 -0
  155. package/src/handlers/sessions.ts +413 -0
  156. package/src/handlers/sharing.ts +198 -0
  157. package/src/handlers/stats.ts +1368 -0
  158. package/src/handlers/team.ts +161 -0
  159. package/src/handlers/uptime.ts +283 -0
  160. package/src/handlers/views.ts +390 -0
  161. package/src/handlers/webhooks.ts +226 -0
  162. package/src/heatmap/index.ts +32 -0
  163. package/src/heatmap/tracking-script.ts +452 -0
  164. package/src/heatmap/types.ts +79 -0
  165. package/src/index.ts +387 -0
  166. package/src/infrastructure/cdk.ts +496 -0
  167. package/src/infrastructure/cloudformation.ts +595 -0
  168. package/src/infrastructure/index.ts +48 -0
  169. package/src/infrastructure/setup.ts +611 -0
  170. package/src/integrations/cloudflare.ts +732 -0
  171. package/src/integrations/hono.ts +589 -0
  172. package/src/integrations/index.ts +27 -0
  173. package/src/integrations/nuxt.ts +78 -0
  174. package/src/integrations/runtime/use-ts-analytics.ts +32 -0
  175. package/src/integrations/stx.ts +138 -0
  176. package/src/jobs/index.ts +127 -0
  177. package/src/lib/crypto-random.ts +41 -0
  178. package/src/lib/ddb-errors.ts +20 -0
  179. package/src/lib/dynamodb.ts +216 -0
  180. package/src/lib/email.ts +38 -0
  181. package/src/lib/ga-import.ts +471 -0
  182. package/src/lib/ga4-api.ts +244 -0
  183. package/src/lib/goals.ts +205 -0
  184. package/src/lib/index.ts +6 -0
  185. package/src/lib/ingest-counters.ts +123 -0
  186. package/src/lib/log.ts +36 -0
  187. package/src/lib/membership.ts +98 -0
  188. package/src/lib/plans.ts +90 -0
  189. package/src/lib/quota.ts +46 -0
  190. package/src/lib/rate-limit.ts +27 -0
  191. package/src/lib/rollups.ts +472 -0
  192. package/src/lib/salt.ts +97 -0
  193. package/src/lib/scheduler.ts +97 -0
  194. package/src/lib/significance.ts +66 -0
  195. package/src/lib/site-retention.ts +56 -0
  196. package/src/local.ts +360 -0
  197. package/src/model-connector.ts +616 -0
  198. package/src/models/AggregatedStats.ts +162 -0
  199. package/src/models/CampaignStats.ts +141 -0
  200. package/src/models/Conversion.ts +135 -0
  201. package/src/models/CustomEvent.ts +123 -0
  202. package/src/models/DeviceStats.ts +105 -0
  203. package/src/models/EventStats.ts +108 -0
  204. package/src/models/GeoStats.ts +112 -0
  205. package/src/models/Goal.ts +105 -0
  206. package/src/models/GoalStats.ts +106 -0
  207. package/src/models/PageStats.ts +152 -0
  208. package/src/models/PageView.ts +277 -0
  209. package/src/models/RealtimeStats.ts +96 -0
  210. package/src/models/ReferrerStats.ts +115 -0
  211. package/src/models/Session.ts +235 -0
  212. package/src/models/Site.ts +115 -0
  213. package/src/models/index.ts +50 -0
  214. package/src/models/orm/index.ts +2043 -0
  215. package/src/models/types.ts +71 -0
  216. package/src/router.ts +521 -0
  217. package/src/sqs-buffering.ts +806 -0
  218. package/src/stacks-integration.ts +527 -0
  219. package/src/tracking-script.ts +944 -0
  220. package/src/tracking.ts +27 -0
  221. package/src/types/analytics.ts +219 -0
  222. package/src/types/api.ts +75 -0
  223. package/src/types/bun-router.d.ts +15 -0
  224. package/src/types/dashboard.ts +62 -0
  225. package/src/types/index.ts +66 -0
  226. package/src/types/stx.d.ts +27 -0
  227. package/src/types/window.d.ts +101 -0
  228. package/src/types.ts +911 -0
  229. package/src/utils/cache.ts +148 -0
  230. package/src/utils/date.ts +118 -0
  231. package/src/utils/filters.ts +71 -0
  232. package/src/utils/geolocation.ts +323 -0
  233. package/src/utils/index.ts +9 -0
  234. package/src/utils/response.ts +180 -0
  235. package/src/utils/timezone-country.ts +242 -0
  236. package/src/utils/user-agent.ts +110 -0
  237. 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
+ }