npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@taxi_tabby/react-eventflow

v0.3.1

Published

React event tracking library with Provider pattern for collecting user interactions and sending to backend

Downloads

909

Readme

@taxi_tabby/react-eventflow

A React-based event tracking library that collects user interactions using the Provider pattern and sends them to your backend.

2025년에도 여전히 웹의 황제 자리를 꽉 잡고 있는 React.
여기 소개할 방법은 React에서 구현할 수 있는 가장 심플하고 매력적인 사용자 추적 방식입니다.

사실 통계나 추적이라는 게 기술적으로 100% 정확할 수는 없어요. 우회 방법이 넘쳐나거든요.
뭐, 인터넷 실명제를 할 것도 아니고 당신의 집에 글로벌 라우터가 있는 것도 아니시겠죠
그냥 빨리 포기하고 현실적인 방법을 찾는 게 속 편합니다.

제가 개발하면서 느낀 “진짜 필요한 정보”는 딱 이 정도였습니다:
- 어디서 들어왔는지
- 지금 어디를 보고 있는지
- 어디로 이동하려 하는지
- 클릭 여부 + 클릭 위치
- 스크롤 여부 + 스크롤 위치
- 마우스가 어디서 어디로 이동했는지
- 좌클릭/우클릭/휠 클릭 여부
- 어딜 보고 있는가 (화면 스크린샷 정보 개발 예정)
- 그리고 가장 중요한 것: 사용자가 누군지는 몰라도, 모든 행동이 한 사용자에게 연결되고 있는가

말했듯 완벽하게 정확할 순 없지만, 사용자 행동의 흐름을 이어주는 것까지는 충분히 가능합니다.
그래서 이벤트 발생 시간과 함께 ‘나름~ 고유한 지문 데이터’를 제공하는 방식으로 정리했습니다.

물론 더 깊게 추적할 수도 있겠지만, 굳이 무거울 필요는 없죠. ip 찾고 dns 조회하고 트래킹하고 국가 위치니 어쩌니 찾고 어휴 무거워.. 
단순하게 여기서 수집한 핵심 데이터만 백엔드로 툭 던져줍시다 
거기서 알아서 잘 처리하겠죠 뭐. 우리는 필요한 건 이미 넘겼으니까요.

로그인 사용자나 더욱 개인을 특정하고자 한다면 onEvent 에서 값 얻어 불러와서 쏴주면 되고 쉽다 쉬워
굳이 무겁게 하려면 백엔드 서버에서 알아서 하시길 권장! 


* 주의 *
trackReferral 기능은 불안정합니다! 잘 동작한다고 생각한다면 따봉

Features

  • ✅ Built on React Provider pattern
  • ✅ Full TypeScript support
  • ✅ Automatic pageview tracking
  • ✅ Automatic navigation tracking
  • ✅ Automatic referral/traffic source tracking
  • ✅ UTM parameter tracking for marketing campaigns
  • ✅ Mouse movement and click tracking
  • ✅ Scroll tracking
  • ✅ Event batching support
  • ✅ Browser fingerprinting for user identification
  • ✅ HMAC signature verification for server-side validation
  • ✅ Lightweight with minimal dependencies

Installation

npm install @taxi_tabby/react-eventflow

or

yarn add @taxi_tabby/react-eventflow

Basic Usage

1. Setup Provider

Add EventFlowProvider at the root of your app:

import { EventFlowProvider } from '@taxi_tabby/react-eventflow';

function App() {
  return (
    <EventFlowProvider
      config={{
        onEvent: async (event) => {
          // Send event to your backend
          await fetch('/api/events', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(event),
          });
        },
        trackPageViews: true,
        trackNavigation: true,
        trackReferral: true,
        trackMouseClick: true,     // Enable click tracking
        trackMouseMoving: false,   // Enable mouse movement tracking (can be noisy)
        trackScroll: true,         // Enable scroll tracking
        debug: process.env.NODE_ENV === 'development',
      }}
    >
      <YourApp />
    </EventFlowProvider>
  );
}

2. Manual Page View Tracking

Use the useEventFlow hook in your components:

import { useEventFlow } from '@taxi_tabby/react-eventflow';

function CustomPage() {
  const { trackPageView } = useEventFlow();

  useEffect(() => {
    trackPageView('/custom-page', 'Custom Page Title');
  }, []);

  return <div>...</div>;
}

Configuration Options

interface EventFlowConfig {
  /** Event callback function (required) */
  onEvent: (event: EventData | EventData[]) => void | Promise<void>;
  
  /** Enable automatic pageview tracking (default: true) */
  trackPageViews?: boolean;
  
  /** Enable automatic navigation tracking (default: true) */
  trackNavigation?: boolean;
  
  /** Enable automatic referral tracking (default: true) */
  trackReferral?: boolean;
  
  /** Enable automatic mouse click tracking (default: false) */
  trackMouseClick?: boolean;
  
  /** Enable automatic mouse moving tracking (default: false) */
  trackMouseMoving?: boolean;
  
  /** Mouse moving throttle interval in ms (default: 100) */
  mouseMovingThrottle?: number;
  
  /** Enable automatic scroll tracking (default: false) */
  trackScroll?: boolean;
  
  /** Scroll throttle interval in ms (default: 200) */
  scrollThrottle?: number;
  
  /** Debug mode (default: false) */
  debug?: boolean;
  
  /** Enable event batching (default: false) */
  enableBatching?: boolean;
  
  /** Batching interval in ms (default: 2000) */
  batchInterval?: number;
  
  /** Enable HMAC signature (default: false) */
  enableHmac?: boolean;
  
  /** HMAC secret key (required when enableHmac is true) */
  hmacSecretKey?: string;
  
  /** HMAC hash algorithm (default: 'sha256') */
  hmacAlgorithm?: 'sha256' | 'sha512' | 'sha384' | 'sha1';
  
  /** HMAC output encoding (default: 'hex') */
  hmacEncoding?: 'hex' | 'base64' | 'base64url';
}

Event Types

All events include a fingerprint field for unique user identification.

PageViewEvent

{
  type: 'pageview',
  timestamp: 1699999999999,
  fingerprint: 'unique-browser-id',
  payload: {
    url: '/products',
    title: 'Products Page',
    referrer: 'https://google.com',
    userAgent: 'Mozilla/5.0...'
  }
}

ReferralEvent

{
  type: 'referral',
  timestamp: 1699999999999,
  fingerprint: 'unique-browser-id',
  payload: {
    currentUrl: 'https://example.com/products?utm_source=google',
    referrer: 'https://google.com/search?q=products',
    referrerDomain: 'google.com',
    sourceType: 'search', // 'direct' | 'external' | 'internal' | 'social' | 'search' | 'email' | 'unknown'
    utm: {
      source: 'google',
      medium: 'cpc',
      campaign: 'summer_sale',
      term: 'products',
      content: 'ad_variant_a'
    },
    queryParams: {
      utm_source: 'google',
      utm_medium: 'cpc',
      // ... all query parameters
    },
    navigation: {
      historyLength: 5,
      isBackNavigation: false,
      navigationType: 'navigate'
    }
  }
}

NavigationEvent

{
  type: 'navigation',
  timestamp: 1699999999999,
  fingerprint: 'unique-browser-id',
  payload: {
    from: '/home',
    to: '/products'
  }
}

MouseMovingEvent

{
  type: 'mouse-moving',
  timestamp: 1699999999999,
  fingerprint: 'unique-browser-id',
  payload: {
    x: 100,
    y: 200,
    pageX: 100,
    pageY: 500
  }
}

MouseClickEvent

{
  type: 'mouse-click',
  timestamp: 1699999999999,
  fingerprint: 'unique-browser-id',
  payload: {
    x: 100,
    y: 200,
    target: 'button',
    targetClass: 'btn-primary',
    targetId: 'submit-btn',
    button: 0
  }
}

ScrollEvent

{
  type: 'scroll',
  timestamp: 1699999999999,
  fingerprint: 'unique-browser-id',
  payload: {
    scrollY: 500,
    scrollX: 0,
    scrollDepth: 25,
    documentHeight: 2000
  }
}

Advanced Features

Event Batching

Enable batching to reduce network requests and payload size:

<EventFlowProvider
  config={{
    onEvent: handleEvents,
    enableBatching: true,
    batchInterval: 5000, // Send every 5 seconds
  }}
>
  <App />
</EventFlowProvider>

When batching is enabled, events are sent in this optimized format:

// Batched events (fingerprint sent once for all events)
{
  fingerprint: 'abc123def456',
  events: [
    {
      type: 'pageview',
      timestamp: 1699999999999,
      payload: { url: '/home', title: 'Home' }
    },
    {
      type: 'mouse-click',
      timestamp: 1699999999999,
      payload: { x: 100, y: 200, target: 'button' }
    },
    {
      type: 'scroll',
      timestamp: 1700000000000,
      payload: { scrollY: 500, scrollDepth: 25 }
    }
  ]
}

This format significantly reduces payload size by including the fingerprint only once instead of with each event.

Interaction Tracking

Enable different types of user interaction tracking:

<EventFlowProvider
  config={{
    onEvent: handleEvents,
    // Basic tracking (enabled by default)
    trackPageViews: true,
    trackNavigation: true,
    trackReferral: true,
    
    // Interaction tracking (disabled by default)
    trackMouseClick: true,        // Track all click events
    trackMouseMoving: true,       // Track mouse movements (can generate many events)
    mouseMovingThrottle: 100,     // Send mouse move event every 100ms
    trackScroll: true,            // Track scroll depth
    scrollThrottle: 200,          // Send scroll event every 200ms
  }}
>
  <App />
</EventFlowProvider>

Note on Performance:

  • Mouse moving tracking can generate a lot of events. Use mouseMovingThrottle to control frequency.
  • Scroll tracking only sends events when scroll depth increases.
  • All trackers use passive event listeners to avoid blocking the main thread.
  • Error handling prevents conflicts with other libraries.

User Identification

Every event automatically includes a browser fingerprint for user identification:

{
  fingerprint: 'abc123def456', // Unique per browser
  type: 'pageview',
  // ...
}

This fingerprint is:

  • Generated using browser characteristics
  • Persistent across sessions
  • Privacy-friendly (no personal data)

HMAC Signature Verification

Enable HMAC signatures to verify events on your server and prevent tampering:

<EventFlowProvider
  config={{
    onEvent: handleEvents,
    enableHmac: true,
    hmacSecretKey: process.env.REACT_APP_HMAC_SECRET, // Keep this secret!
    hmacAlgorithm: 'sha256',    // Default: 'sha256' (options: 'sha256', 'sha512', 'sha384', 'sha1')
    hmacEncoding: 'hex',        // Default: 'hex' (options: 'hex', 'base64', 'base64url')
  }}
>
  <App />
</EventFlowProvider>

⚠️ Security Warning: Client-side environment variables (REACT_APP_*, NEXT_PUBLIC_*) are embedded in your bundle at build time and can be viewed by anyone!

Recommended Approach: Implement a proper key management infrastructure on your backend for maximum security and effectiveness.

Instead of embedding HMAC keys directly in your client bundle, your backend should:

  • Generate and distribute session-specific temporary keys to authenticated clients
  • Derive temporary keys from a master secret that never leaves the server
  • Implement automatic key rotation and expiration (e.g., 1-hour TTL)
  • Store session keys in a secure backend store (Redis, database, or in-memory with proper session management)

This approach follows Public Key Infrastructure (PKI) principles, where:

  • The master key remains on the server and is never exposed
  • Each client session receives a unique derived key
  • Compromised keys have limited scope and impact
  • Keys can be revoked and rotated without affecting all users

Benefits:

  • ✅ Master key stays on server only (never exposed)
  • ✅ Different key per session (minimizes impact if key is compromised)
  • ✅ Automatic key expiration and rotation
  • ✅ Can integrate with user authentication
  • ✅ Centralized key management and audit capabilities
  • ✅ Compliance with security best practices

When HMAC is enabled, events include a signature:

// Single event
{
  type: 'pageview',
  timestamp: 1699999999999,
  fingerprint: 'abc123def456',
  hmac: 'a1b2c3d4e5f6...', // HMAC signature of entire event data
  payload: { url: '/home' }
}

// Batched events
{
  fingerprint: 'abc123def456',
  hmac: 'a1b2c3d4e5f6...', // HMAC signature of entire batch data
  events: [...]
}

The HMAC signature covers the entire event data (type, timestamp, fingerprint, and payload) to ensure complete data integrity.

Server-side verification example (Node.js):

import crypto from 'crypto';

function verifyEventHmac(event: EventData, secretKey: string): boolean {
  const { hmac, ...eventData } = event;
  
  // Create normalized JSON string from event data (excluding hmac)
  const normalizedData = JSON.stringify(eventData);
  
  const expectedHmac = crypto
    .createHmac('sha256', secretKey)
    .update(normalizedData)
    .digest('hex');
  
  // Timing-safe comparison
  return crypto.timingSafeEqual(
    Buffer.from(hmac),
    Buffer.from(expectedHmac)
  );
}

// For batched events
function verifyBatchHmac(batch: BatchedEvents, secretKey: string): boolean {
  const { hmac, ...batchData } = batch;
  
  // Create normalized JSON string from batch data (excluding hmac)
  const normalizedData = JSON.stringify(batchData);
  
  const expectedHmac = crypto
    .createHmac('sha256', secretKey)
    .update(normalizedData)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(hmac),
    Buffer.from(expectedHmac)
  );
}

// In your API handler
app.post('/api/events', (req, res) => {
  const event = req.body;
  
  if (!verifyEventHmac(event, process.env.HMAC_SECRET)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  // Process valid event
  // ...
});

Why use HMAC?

  • Prevents event tampering - ensures complete data integrity of type, timestamp, fingerprint, and payload
  • Validates that events come from your application
  • Detects any modifications to event data during transmission
  • Simple server-side verification
  • Cryptographically secure using industry-standard algorithms

Best Practices:

  • Use sha256 or sha512 algorithm for best security
  • Keep your hmacSecretKey secret and never expose it in client code
  • Use environment variables to store the secret key
  • The same secret key must be used on both client and server
  • Use hex encoding for compatibility, base64url for shorter signatures

Eaxample

next.js

'use client';

import { EventFlowProvider as BaseEventFlowProvider } from '@taxi_tabby/react-eventflow';
import { ReactNode } from 'react';

interface EventFlowWrapperProps {
  children: ReactNode;
}

export function EventFlowProvider({ children }: EventFlowWrapperProps) {
  return (
    <BaseEventFlowProvider
      config={{
        trackPageViews: true,
        trackNavigation: true,
        trackReferral: true,
        trackMouseClick: true,
        trackMouseMoving: true,
        trackScroll: true,

        enableBatching: true,
        batchInterval: 5000,
        mouseMovingThrottle: 1000,
        scrollThrottle: 1000,

        // HMAC signature for server verification
        enableHmac: true,
        hmacSecretKey: process.env.NEXT_PUBLIC_HMAC_SECRET!,
        hmacAlgorithm: 'sha256',
        hmacEncoding: 'hex',

        debug: false,
        onEvent: async (event) => {
          // Check if it's a batched event
          if ('events' in event) {
            // Batched events: { fingerprint: string, hmac: string, events: Array }
            console.log('Batch received:', event.fingerprint, event.events.length, 'events');
            // Send batched events to your backend
            await fetch('/api/events/batch', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify(event),
            });
          } else {
            // Single event: { fingerprint: string, hmac: string, type: string, timestamp: number, payload: any }
            console.log('Single event:', event.fingerprint, event.type);
            await fetch('/api/events', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify(event),
            });
          }
        },
      }}
    >
      {children}
    </BaseEventFlowProvider>
  );
}

License

MIT

Contributing

Issues and PRs are always welcome!