@@ -14,28 +14,54 @@ const IS_ENABLED =
1414
1515const IS_DEV = import . meta. env . VITE_BUILD_ENV !== 'production' ;
1616
17+ // Guards against duplicate listener registration on repeated initAmplitude calls.
18+ let humanWaitSetup = false ;
19+ // Buffered network value set before Amplitude loads; replayed on first human interaction.
20+ let pendingNetwork : string | null = null ;
21+
1722/**
18- * Anti-bot configuration: Events are queued but not sent until a human interaction is detected.
19- * Sessions are classified as human on the first DOM interaction (scroll, mousemove, keydown, touchstart).
23+ * Anti-bot protection: defers ampli.load() until a genuine human gesture is detected.
2024 */
21- const ANTI_BOT_CONFIG = {
22- // Regular flush interval once the session is classified as human
23- REGULAR_FLUSH_INTERVAL_MS : 1000 ,
24- // Initial flush settings — effectively disabled so events queue locally until bot check passes
25- INITIAL_FLUSH_INTERVAL_MS : 3600000 , // 1 hour
26- INITIAL_QUEUE_SIZE : 500 ,
27- } as const ;
25+ export function initAmplitude ( ) : void {
26+ const consentStatus = getAmplitudeConsentStatus ( ) ;
27+
28+ if ( ampli . isLoaded || humanWaitSetup || consentStatus === 'declined' ) {
29+ return ;
30+ }
2831
29- let isBotCleared = false ;
32+ if ( navigator . webdriver ) {
33+ return ;
34+ }
3035
31- export async function initAmplitude ( ) {
32- const consentStatus = getAmplitudeConsentStatus ( ) ;
36+ humanWaitSetup = true ;
37+ waitForHumanInteraction ( ) ;
38+ }
39+
40+ const HUMAN_SIGNAL_EVENTS = [ 'pointerdown' , 'wheel' , 'keydown' , 'touchstart' , 'copy' ] as const ;
41+
42+ function waitForHumanInteraction ( ) : void {
43+ const controller = new AbortController ( ) ;
44+ let handled = false ;
45+
46+ function onHumanInteraction ( ) {
47+ if ( handled ) return ;
48+ handled = true ;
49+ controller . abort ( ) ;
50+ void loadAmplitude ( ) ;
51+ }
3352
53+ const options = { passive : true , signal : controller . signal } as const ;
54+ for ( const event of HUMAN_SIGNAL_EVENTS ) {
55+ window . addEventListener ( event , onHumanInteraction , options ) ;
56+ }
57+ }
58+
59+ async function loadAmplitude ( ) : Promise < void > {
60+ const consentStatus = getAmplitudeConsentStatus ( ) ;
3461 if ( ampli . isLoaded || consentStatus === 'declined' ) {
3562 return ;
3663 }
3764
38- // Load Amplitude with anti-bot flush settings
3965 await ampli . load ( {
4066 environment : 'iotaexplorer' ,
4167 disabled : ! IS_ENABLED ,
@@ -55,77 +81,30 @@ export async function initAmplitude() {
5581 pageUrlEnrichment : IS_ENABLED ,
5682 } ,
5783 logLevel : LogLevel . None ,
58- flushIntervalMillis : ANTI_BOT_CONFIG . INITIAL_FLUSH_INTERVAL_MS ,
59- flushQueueSize : ANTI_BOT_CONFIG . INITIAL_QUEUE_SIZE ,
6084 identityStorage : 'localStorage' ,
6185 } ,
6286 } ,
6387 } ) . promise ;
6488
6589 ampli . client . add ( attachEnvironmentPlugin ( IS_DEV ) ) ;
6690
67- setupAntiBotProtection ( ) ;
68- }
69-
70- const HUMAN_SIGNAL_EVENTS = [ 'scroll' , 'mousemove' , 'keydown' , 'touchstart' ] as const ;
71-
72- /**
73- * Sets up anti-bot protection:
74- * 1. Queues all events locally (1-hour flush interval prevents premature sends)
75- * 2. Classifies the session as human on the first DOM interaction and enables regular flushing
76- * 3. On page exit, beacon-flushes only if the session was classified as human
77- */
78- function setupAntiBotProtection ( ) {
79- let flushInterval : ReturnType < typeof setInterval > | null = null ;
80-
81- function enableFlushing ( ) {
82- if ( isBotCleared ) {
83- return ;
84- }
85- isBotCleared = true ;
86- ampli . flush ( ) ;
87- flushInterval = setInterval ( ( ) => {
88- if ( ampli . isLoaded ) {
89- ampli . flush ( ) ;
90- }
91- } , ANTI_BOT_CONFIG . REGULAR_FLUSH_INTERVAL_MS ) ;
91+ if ( pendingNetwork !== null ) {
92+ setAmplitudeIdentity ( pendingNetwork ) ;
9293 }
9394
94- const humanSignalController = new AbortController ( ) ;
95- const options = { passive : true , signal : humanSignalController . signal } as const ;
96- const handler = ( ) => {
97- humanSignalController . abort ( ) ;
98- enableFlushing ( ) ;
99- } ;
100- for ( const event of HUMAN_SIGNAL_EVENTS ) {
101- window . addEventListener ( event , handler , options ) ;
102- }
103-
104- // Flush on page exit only if the session was classified as human.
10595 window . addEventListener (
10696 'pagehide' ,
10797 ( ) => {
108- humanSignalController . abort ( ) ;
109-
110- if ( flushInterval ) {
111- clearInterval ( flushInterval ) ;
112- }
113-
114- if ( isBotCleared ) {
115- ampli . client . setTransport ( 'beacon' ) ;
116- ampli . flush ( ) ;
117- }
98+ ampli . client . setTransport ( 'beacon' ) ;
99+ ampli . flush ( ) ;
118100 } ,
119101 { once : true } ,
120102 ) ;
121103}
122104
123- /**
124- * Set the Amplitude user identity with the current network context.
125- * Updates user property: network.
126- * This allows filtering and segmenting analytics events by network dimension.
127- */
128105export function setAmplitudeIdentity ( network : string ) : void {
106+ pendingNetwork = network ;
107+
129108 if ( ! ampli . isLoaded ) {
130109 return ;
131110 }
0 commit comments