@m13v/seo-components 0.31.2 → 0.31.3

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/lib/track.ts +35 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m13v/seo-components",
3
- "version": "0.31.2",
3
+ "version": "0.31.3",
4
4
  "scripts": {
5
5
  "build:css": "tailwind -i src/_build.css -o dist/styles.css --minify",
6
6
  "lint:mobile-spans": "node scripts/lint-mobile-spans.mjs",
package/src/lib/track.ts CHANGED
@@ -8,6 +8,38 @@
8
8
 
9
9
  import { captureFromWindow } from "./analytics-context";
10
10
 
11
+ // Rage-click dedup window. A real visitor revisiting the page later still
12
+ // records; only same-event+same-destination+same-page firings within this
13
+ // window collapse to one. 2026-05-03: claude-meter.com had 11 reported
14
+ // `get_started_click` events in 24h that were actually 2 unique users
15
+ // rage-mashing the install button (5-6 clicks within 4-14s each). Dedup
16
+ // at the helper layer catches every callsite (GetStartedCTA, InlineCta,
17
+ // StickyBottomCta, InstallEmailGate, ClaudeMeterCta, BookCallCTA,
18
+ // SiteNavbar, and any custom caller in consumer sites).
19
+ const _DEDUP_WINDOW_MS = 1500;
20
+ const _lastFire = new Map<string, number>();
21
+
22
+ function _shouldSuppressRecent(key: string, windowMs = _DEDUP_WINDOW_MS): boolean {
23
+ if (typeof window === "undefined") return false;
24
+ const now = Date.now();
25
+ const last = _lastFire.get(key);
26
+ if (last != null && now - last < windowMs) return true;
27
+ _lastFire.set(key, now);
28
+ // Keep the map from growing unbounded on long-lived SPAs.
29
+ if (_lastFire.size > 256) {
30
+ const cutoff = now - 60_000;
31
+ for (const [k, t] of _lastFire) {
32
+ if (t < cutoff) _lastFire.delete(k);
33
+ }
34
+ }
35
+ return false;
36
+ }
37
+
38
+ function _dedupKey(event: string, destination: string | undefined): string {
39
+ const page = typeof window !== "undefined" ? window.location.pathname : "";
40
+ return event + "::" + (destination || "") + "::" + page;
41
+ }
42
+
11
43
  /**
12
44
  * Rewrite a booking URL (Cal.com, Calendly, etc.) so the booking webhook can
13
45
  * attribute the booking back to the specific landing page it came from.
@@ -81,6 +113,7 @@ export interface ScheduleClickProps {
81
113
  */
82
114
  export function trackScheduleClick(props: ScheduleClickProps): void {
83
115
  const { destination, site, section, text, component, extra } = props;
116
+ if (_shouldSuppressRecent(_dedupKey("schedule_click", destination))) return;
84
117
  captureFromWindow("schedule_click", {
85
118
  ...(extra || {}),
86
119
  destination,
@@ -119,6 +152,7 @@ export interface GetStartedClickProps {
119
152
  */
120
153
  export function trackGetStartedClick(props: GetStartedClickProps): void {
121
154
  const { destination, site, section, text, component, extra } = props;
155
+ if (_shouldSuppressRecent(_dedupKey("get_started_click", destination))) return;
122
156
  captureFromWindow("get_started_click", {
123
157
  ...(extra || {}),
124
158
  destination,
@@ -176,6 +210,7 @@ export interface CrossProductClickProps {
176
210
  */
177
211
  export function trackCrossProductClick(props: CrossProductClickProps): void {
178
212
  const { destination, site, targetProduct, section, text, component, extra } = props;
213
+ if (_shouldSuppressRecent(_dedupKey("cross_product_click", destination))) return;
179
214
  captureFromWindow("cross_product_click", {
180
215
  ...(extra || {}),
181
216
  destination,