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

sse-shared-worker-react-hook

v1.0.7

Published

Type-safe Server-Sent Events (SSE) hook for React with token authentication and retry logic

Readme

React SSE Hook

A TypeScript package for using Server-Sent Events (SSE) in React with full support for authentication tokens, retry logic, and type safety.

Features

  • Type-Safe: Fully written in TypeScript
  • Token Authentication: Bearer token support
  • Retry Logic: Retry with exponential backoff and max retry delay
  • Customizable: Configurable for different needs
  • React Hook: Easy to use with React Hooks
  • Shared Worker Support: Shared Worker support for sharing a single connection across all tabs

Installation

npm install sse-shared-worker-react-hook
# or
yarn add sse-shared-worker-react-hook
# or
pnpm install sse-shared-worker-react-hook

Usage

Simple Example

import { useSSE } from 'sse-shared-worker-react-hook';

function MyComponent() {
  const { status, lastEvent, events, error } = useSSE('/api/events');

  if (status === 'connecting') {
    return <div>Connecting...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <div>
      <p>Status: {status}</p>
      <p>Event count: {events.length}</p>
      {lastEvent && (
        <div>
          <h3>Last event:</h3>
          <pre>{JSON.stringify(lastEvent.data, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

With Token Authentication

import { useSSE } from 'sse-shared-worker-react-hook';

function AuthenticatedComponent() {
  const token = 'your-auth-token';
  
  const { status, lastEvent, events, error } = useSSE('/api/events', {
    token,
    maxRetries: 5,
    maxRetryDelay: 30000, // 30 seconds
    initialRetryDelay: 1000, // 1 second
  });

  return (
    <div>
      {/* ... */}
    </div>
  );
}

With Type Safety

import { useSSE } from 'sse-shared-worker-react-hook';

interface NotificationData {
  id: string;
  message: string;
  timestamp: number;
}

function TypedComponent() {
  const { lastEvent, events } = useSSE<NotificationData>('/api/notifications', {
    token: 'your-token',
  });

  // lastEvent.data is now typed as NotificationData
  return (
    <div>
      {events.map((event, index) => (
        <div key={index}>
          <p>{event.data.message}</p>
          <small>{new Date(event.data.timestamp).toLocaleString()}</small>
        </div>
      ))}
    </div>
  );
}

With Type-Safe Event Types

import { useSSE } from 'sse-shared-worker-react-hook';

interface MessageData {
  text: string;
  userId: string;
}

interface ErrorData {
  code: string;
  message: string;
}

// Define allowed event types
type EventTypes = 'message' | 'error' | 'update';

function TypedEventComponent() {
  const { lastEvent, events } = useSSE<MessageData | ErrorData, EventTypes>(
    '/api/events',
    {
      token: 'your-token',
    }
  );

  // lastEvent.type is now typed as EventTypes
  // TypeScript will enforce that only 'message' | 'error' | 'update' are valid
  return (
    <div>
      {events.map((event, index) => {
        if (event.type === 'message') {
          // TypeScript knows event.data is MessageData here
          return <div key={index}>{event.data.text}</div>;
        } else if (event.type === 'error') {
          // TypeScript knows event.data is ErrorData here
          return <div key={index}>Error: {event.data.message}</div>;
        }
        return null;
      })}
    </div>
  );
}

Manual Connection Control

import { useSSE } from 'sse-shared-worker-react-hook';

function ControlledComponent() {
  const { status, close, reconnect } = useSSE('/api/events', {
    token: 'your-token',
    autoReconnect: false, // Disable automatic reconnect
  });

  return (
    <div>
      <p>Status: {status}</p>
      <button onClick={close}>Disconnect</button>
      <button onClick={reconnect}>Reconnect</button>
    </div>
  );
}

With Custom Headers

import { useSSE } from 'sse-shared-worker-react-hook';

function CustomHeadersComponent() {
  const { status, lastEvent } = useSSE('/api/events', {
    token: 'your-token',
    headers: {
      'X-Custom-Header': 'custom-value',
      'X-Client-Version': '1.0.0',
    },
  });

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

With Custom Retry Delay Function

import { useSSE } from 'sse-shared-worker-react-hook';

function CustomRetryComponent() {
  const { status, retryCount } = useSSE('/api/events', {
    token: 'your-token',
    maxRetries: 10,
    maxRetryDelay: 60000, // 60 seconds
    retryDelayFn: (attempt) => {
      // Custom logic: linear backoff
      return attempt * 2000; // 2s, 4s, 6s, ...
    },
  });

  return (
    <div>
      <p>Status: {status}</p>
      <p>Retry count: {retryCount}</p>
    </div>
  );
}

Using with Shared Worker

Benefits of using Shared Worker:

  • ✅ Only one SSE connection in the entire system (not for each tab)
  • ✅ Data is shared between all tabs and windows
  • ✅ Reduced resource consumption and bandwidth
  • ✅ Automatic synchronization between tabs

Setting up Shared Worker

First, you need to place the Shared Worker file in your project:

For Vite:

// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { copyFileSync } from 'fs';
import { join } from 'path';

export default defineConfig({
  plugins: [
    react(),
    {
      name: 'copy-shared-worker',
      buildStart() {
        copyFileSync(
          join(__dirname, 'node_modules/sse-shared-worker-react-hook/dist-hook/shared-worker.js'),
          join(__dirname, 'public/shared-worker.js')
        );
      },
    },
  ],
});

For Create React App: Copy the dist/shared-worker.js file to the public folder.

For Webpack:

// webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        {
          from: 'node_modules/sse-shared-worker-react-hook/dist-hook/shared-worker.js',
          to: 'shared-worker.js',
        },
      ],
    }),
  ],
};

Example using Shared Worker

import { useSSEWithSharedWorker } from 'sse-shared-worker-react-hook';

function SharedWorkerComponent() {
  const { status, lastEvent, events, error } = useSSEWithSharedWorker(
    '/api/events',
    {
      token: 'your-auth-token',
      maxRetries: 5,
      maxRetryDelay: 30000,
    },
    '/shared-worker.js' // Path to Shared Worker file
  );

  return (
    <div>
      <p>Status: {status}</p>
      <p>Event count: {events.length}</p>
      {lastEvent && (
        <div>
          <h3>Last event:</h3>
          <pre>{JSON.stringify(lastEvent.data, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

Example: Using in Multiple Tabs

// Tab 1, Tab 2, Tab 3 - All use the same connection!

// Tab 1
function Tab1Component() {
  const { events } = useSSEWithSharedWorker('/api/notifications', {
    token: localStorage.getItem('token'),
  });

  return (
    <div>
      <h2>Tab 1</h2>
      {events.map((e, i) => (
        <div key={i}>{e.data.message}</div>
      ))}
    </div>
  );
}

// Tab 2 - Receives the same data!
function Tab2Component() {
  const { events } = useSSEWithSharedWorker('/api/notifications', {
    token: localStorage.getItem('token'),
  });

  return (
    <div>
      <h2>Tab 2</h2>
      {events.map((e, i) => (
        <div key={i}>{e.data.message}</div>
      ))}
    </div>
  );
}

Type Safety with Shared Worker

import { useSSEWithSharedWorker } from 'sse-shared-worker-react-hook';

interface StockPrice {
  symbol: string;
  price: number;
  change: number;
}

function StockTracker() {
  const { lastEvent, events } = useSSEWithSharedWorker<StockPrice>(
    '/api/stock-prices',
    {
      token: 'your-token',
    }
  );

  // lastEvent.data is typed as StockPrice
  return (
    <div>
      {events.map((event, index) => (
        <div key={index}>
          <h3>{event.data.symbol}</h3>
          <p>Price: ${event.data.price}</p>
          <p>Change: {event.data.change}%</p>
        </div>
      ))}
    </div>
  );
}

Type-Safe Event Types with Shared Worker

import { useSSEWithSharedWorker } from 'sse-shared-worker-react-hook';

interface NotificationData {
  title: string;
  body: string;
}

type NotificationEventTypes = 'notification' | 'alert' | 'system';

function NotificationComponent() {
  const { lastEvent, events } = useSSEWithSharedWorker<
    NotificationData,
    NotificationEventTypes
  >('/api/notifications', {
    token: 'your-token',
  });

  // event.type is now typed as NotificationEventTypes
  return (
    <div>
      {events.map((event, index) => (
        <div key={index}>
          <h4>Type: {event.type}</h4>
          <p>{event.data.title}</p>
          <p>{event.data.body}</p>
        </div>
      ))}
    </div>
  );
}

API Reference

useSSE<T, K>(url, options?)

A React Hook for connecting to an SSE endpoint (each component has a separate connection).

Generic Parameters

  • T - Type of the event data (default: any)
  • K - Type of the event type (default: string)

Parameters

  • url: string | null - URL endpoint for SSE
  • options?: SSEOptions - Configuration options

Returns

SSEReturn<T, K> - See below for details.

useSSEWithSharedWorker<T, K>(url, options?, workerPath?)

A React Hook for connecting to an SSE endpoint using Shared Worker (one connection for all tabs).

Generic Parameters

  • T - Type of the event data (default: any)
  • K - Type of the event type (default: string)

Parameters

  • url: string | null - URL endpoint for SSE
  • options?: SSEOptions - Configuration options
  • workerPath?: string - Path to Shared Worker file (default: '/shared-worker.js')

Returns

SSEReturn<T, K> - See below for details.

SSEReturn<T, K>

interface SSEReturn<T, K extends string = string> {
  status: SSEStatus;                    // Connection status
  lastEvent: SSEEvent<T, K> | null;      // Last received event
  events: SSEEvent<T, K>[];              // All received events
  error: Error | null;                   // Error (if any)
  close: () => void;                     // Close connection
  reconnect: () => void;                 // Reconnect
  retryCount: number;                    // Retry attempt count
}

SSEOptions

interface SSEOptions {
  token?: string;                    // Bearer token for authentication
  maxRetryDelay?: number;            // Maximum retry delay (ms) - default: 30000
  initialRetryDelay?: number;        // Initial retry delay (ms) - default: 1000
  maxRetries?: number;               // Maximum retry count - default: 5
  headers?: Record<string, string>;  // Additional headers
  autoReconnect?: boolean;           // Auto reconnect - default: true
  retryDelayFn?: (attempt: number) => number; // Retry delay calculation function
}

SSEStatus

type SSEStatus = 
  | 'connecting'    // Connecting
  | 'connected'     // Connected
  | 'disconnected'  // Disconnected
  | 'error'         // Error
  | 'closed';       // Closed

SSEEvent<T, K>

interface SSEEvent<T = any, K extends string = string> {
  type: K;            // Event type (type-safe with generic K)
  data: T;            // Event data (type-safe with generic T)
  id?: string;        // Event ID (from server)
  timestamp: number;  // Receive timestamp
}

Example:

// Without generics (defaults to any, string)
const event: SSEEvent = {
  type: 'message',
  data: { text: 'Hello' },
  timestamp: Date.now()
};

// With data type only
const typedEvent: SSEEvent<{ text: string }> = {
  type: 'message',
  data: { text: 'Hello' },
  timestamp: Date.now()
};

// With both data and event type
type EventTypes = 'message' | 'error' | 'update';
const fullyTypedEvent: SSEEvent<{ text: string }, EventTypes> = {
  type: 'message', // TypeScript enforces: must be 'message' | 'error' | 'update'
  data: { text: 'Hello' },
  timestamp: Date.now()
};

Advanced Examples

State Management with SSE

import { useEffect } from 'react';
import { useSSE } from 'sse-shared-worker-react-hook';

interface UserStatus {
  userId: string;
  online: boolean;
}

function UserStatusComponent() {
  const [users, setUsers] = useState<Map<string, boolean>>(new Map());
  
  const { lastEvent } = useSSE<UserStatus, 'user-status'>('/api/user-status', {
    token: localStorage.getItem('token') || undefined,
  });

  useEffect(() => {
    if (lastEvent) {
      setUsers((prev) => {
        const next = new Map(prev);
        next.set(lastEvent.data.userId, lastEvent.data.online);
        return next;
      });
    }
  }, [lastEvent]);

  return (
    <div>
      {Array.from(users.entries()).map(([userId, online]) => (
        <div key={userId}>
          User {userId}: {online ? 'Online' : 'Offline'}
        </div>
      ))}
    </div>
  );
}

Error Handling

import { useSSE } from 'sse-shared-worker-react-hook';

function ErrorHandlingComponent() {
  const { status, error, retryCount, reconnect } = useSSE('/api/events', {
    token: 'your-token',
    maxRetries: 3,
  });

  if (status === 'error' && retryCount >= 3) {
    return (
      <div>
        <p>Connection failed. Please try again.</p>
        <button onClick={reconnect}>Retry</button>
      </div>
    );
  }

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

Important Notes

  1. Token Authentication: When using token, the package uses the fetch API (because EventSource doesn't support custom headers).

  2. Retry Logic: Retry is performed with exponential backoff. You can define your own logic with retryDelayFn.

  3. Memory Management: Events are stored in the events array. To prevent excessive memory usage, you can periodically clear the array. The package automatically limits events to 100 items.

  4. Cleanup: The connection is automatically closed when the component unmounts.

  5. Shared Worker:

    • To use useSSEWithSharedWorker, you must place the Shared Worker file in the public folder
    • Shared Worker creates only one SSE connection and shares data across all tabs
    • When the last tab is closed, the Shared Worker is automatically closed
    • Received data is stored in the Shared Worker and sent to new tabs that connect

License

MIT