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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@smarthivelabs-devs/hive-socket-react

v1.1.0

Published

React SDK for Hive-Socket — real-time hooks and provider for web apps

Downloads

60

Readme

@smarthivelabs-devs/hive-socket-react

React SDK for Hive-Socket — real-time hooks and provider for Next.js and React web apps.

Install

npm install @smarthivelabs-devs/hive-socket-react socket.io-client
# or
yarn add @smarthivelabs-devs/hive-socket-react socket.io-client
# or
pnpm add @smarthivelabs-devs/hive-socket-react socket.io-client

Peer dependencies: react >= 18, socket.io-client ^4


Setup

1. Wrap your app with the provider

The provider manages the WebSocket connection lifecycle. It reconnects automatically when the token changes (e.g. after a token refresh).

Next.js App Router:

// app/providers.tsx
'use client';

import { HiveSocketProvider } from '@smarthivelabs-devs/hive-socket-react';

export function Providers({
  token,
  children,
}: {
  token: string;
  children: React.ReactNode;
}) {
  return (
    <HiveSocketProvider url="https://socket.smarthivelabs.dev" token={token}>
      {children}
    </HiveSocketProvider>
  );
}
// app/layout.tsx
import { getAccessToken } from '@/lib/auth'; // your SmartHive Auth call
import { Providers } from './providers';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const token = await getAccessToken();

  return (
    <html>
      <body>
        <Providers token={token}>{children}</Providers>
      </body>
    </html>
  );
}

The provider must be a Client Component. In App Router, always create a providers.tsx wrapper as shown — do not add 'use client' to layout.tsx.

React (Vite / CRA):

// main.tsx
import { HiveSocketProvider } from '@smarthivelabs-devs/hive-socket-react';

root.render(
  <HiveSocketProvider url="https://socket.smarthivelabs.dev" token={accessToken}>
    <App />
  </HiveSocketProvider>
);

2. Use hooks anywhere inside the provider

import { useHiveSocket, useMessages, useNotifications } from '@smarthivelabs-devs/hive-socket-react';

Hooks

useHiveSocket

Connection status and all send actions.

const {
  status,               // 'connecting' | 'connected' | 'disconnected'
  socketId,             // string | null

  joinRoom,             // (roomId: string) => void
  leaveRoom,            // (roomId: string) => void
  sendMessage,          // (roomId: string, type: string, payload: Record<string, unknown>) => void
  updatePresence,       // (status: 'online' | 'away' | 'offline') => void
  fetchHistory,         // (roomId: string, limit?: number, before?: string) => void
  markNotificationRead, // (notificationId: string) => void
} = useHiveSocket();

Connection status indicator:

function ConnectionStatus() {
  const { status } = useHiveSocket();

  return (
    <span style={{ color: status === 'connected' ? 'green' : 'gray' }}>
      {status === 'connected' ? 'Live' : 'Connecting...'}
    </span>
  );
}

useMessages(roomId)

Subscribe to real-time messages in a room. Automatically joins on mount, fetches the last 50 messages as history, appends new messages as they arrive, and leaves the room on unmount.

const messages = useMessages(roomId); // HiveMessage[]
import { useMessages, useHiveSocket } from '@smarthivelabs-devs/hive-socket-react';
import { useState } from 'react';

function ChatRoom({ roomId }: { roomId: string }) {
  const { status, sendMessage } = useHiveSocket();
  const messages = useMessages(roomId);
  const [text, setText] = useState('');

  const send = () => {
    if (!text.trim()) return;
    sendMessage(roomId, 'chat.message', { text });
    setText('');
  };

  return (
    <div>
      <div className="messages">
        {messages.map((msg) => (
          <div key={msg._eid}>
            <strong>{msg.senderId}</strong>
            <span>{msg.payload.text as string}</span>
            <time>{new Date(msg.timestamp).toLocaleTimeString()}</time>
          </div>
        ))}
      </div>
      <div className="input-row">
        <input
          value={text}
          onChange={(e) => setText(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && send()}
          placeholder="Type a message..."
        />
        <button onClick={send} disabled={status !== 'connected'}>
          Send
        </button>
      </div>
    </div>
  );
}

HiveMessage shape:

interface HiveMessage {
  type: string;                      // e.g. 'chat.message', 'quiz.answer', 'vote.cast'
  senderId: string;                  // userId of the sender
  roomId: string;
  payload: Record<string, unknown>;  // your custom data
  timestamp: number;                 // Unix ms
  _eid: string;                      // unique event ID — use as React key
}

Message type conventions:

| type | Use case | |--------|----------| | chat.message | Regular chat message | | quiz.answer | Quiz submission | | vote.cast | Vote event | | doc.update | Collaborative document edit | | presence.note | Presence-related message |


usePresence(userId)

Track another user's live presence status.

const presence = usePresence(userId);
// { status: 'online' | 'away' | 'offline', lastSeen: string } | null
import { usePresence } from '@smarthivelabs-devs/hive-socket-react';

function UserAvatar({ userId, avatarUrl }: { userId: string; avatarUrl: string }) {
  const presence = usePresence(userId);
  const isOnline = presence?.status === 'online';

  return (
    <div style={{ position: 'relative', display: 'inline-block' }}>
      <img src={avatarUrl} style={{ borderRadius: '50%', width: 40, height: 40 }} />
      <span
        style={{
          position: 'absolute',
          bottom: 0,
          right: 0,
          width: 10,
          height: 10,
          borderRadius: '50%',
          backgroundColor: isOnline ? '#22c55e' : '#9ca3af',
          border: '2px solid white',
        }}
      />
    </div>
  );
}

Update your own presence status:

const { updatePresence } = useHiveSocket();

// When user goes away (e.g. tab visibility change)
document.addEventListener('visibilitychange', () => {
  updatePresence(document.hidden ? 'away' : 'online');
});

useNotifications

Receive real-time notifications pushed from your backends. Pending (unread) notifications are loaded automatically on connect.

const {
  notifications,  // HiveNotification[]
  unreadCount,    // number
  markRead,       // (notificationId: string) => void
} = useNotifications();
import { useNotifications } from '@smarthivelabs-devs/hive-socket-react';

function NotificationPanel() {
  const { notifications, unreadCount, markRead } = useNotifications();

  return (
    <div>
      <h3>Notifications {unreadCount > 0 && <span>({unreadCount})</span>}</h3>
      {notifications.map((n) => (
        <div
          key={n.id}
          onClick={() => !n.read && markRead(n.id)}
          style={{ opacity: n.read ? 0.5 : 1, cursor: n.read ? 'default' : 'pointer' }}
        >
          <p style={{ fontWeight: n.read ? 'normal' : 'bold' }}>{n.title}</p>
          <p>{n.body}</p>
          <time>{new Date(n.createdAt).toLocaleString()}</time>
        </div>
      ))}
      {notifications.length === 0 && <p>No notifications</p>}
    </div>
  );
}

Notification bell with badge:

function NotificationBell() {
  const { unreadCount } = useNotifications();

  return (
    <div style={{ position: 'relative', display: 'inline-block' }}>
      <span>🔔</span>
      {unreadCount > 0 && (
        <span
          style={{
            position: 'absolute',
            top: -4,
            right: -4,
            background: 'red',
            color: 'white',
            borderRadius: '50%',
            width: 18,
            height: 18,
            fontSize: 11,
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          {unreadCount > 99 ? '99+' : unreadCount}
        </span>
      )}
    </div>
  );
}

HiveNotification shape:

interface HiveNotification {
  id: string;
  title: string;
  body: string;
  type: 'info' | 'success' | 'warning' | 'error' | 'message';
  metadata: Record<string, unknown>;
  sourceService?: string;   // which backend sent it
  read: boolean;
  readAt: string | null;    // ISO date string
  createdAt: string;        // ISO date string
}

useWebRTC

Peer-to-peer audio, video, screen sharing, and data channels over WebRTC. The existing Socket.IO connection is used for signaling (offer/answer/ICE) — the actual media streams flow directly between clients. No extra dependencies needed.

const {
  callState,        // 'idle' | 'calling' | 'incoming' | 'connecting' | 'active' | 'ended'
  currentCall,      // HiveWebRtcCall | null

  localStream,      // MediaStream | null  — your camera/mic
  remoteStream,     // MediaStream | null  — the other peer's stream
  screenStream,     // MediaStream | null  — your screen share
  dataChannel,      // RTCDataChannel | null

  isAudioMuted,     // boolean
  isVideoOff,       // boolean
  isSharingScreen,  // boolean

  startCall,        // (targetUserId: string, callType: WebRtcCallType) => Promise<void>
  acceptCall,       // () => Promise<void>
  rejectCall,       // () => void
  endCall,          // () => void

  toggleAudio,      // () => void
  toggleVideo,      // () => void
  startScreenShare, // () => Promise<void>
  stopScreenShare,  // () => void
  sendData,         // (data: string | ArrayBuffer) => void
} = useWebRTC();

WebRtcCallType: 'audio' | 'video' | 'screen' | 'data'

Video call example:

import { useWebRTC } from '@smarthivelabs-devs/hive-socket-react';
import { useRef, useEffect } from 'react';

function VideoCall({ targetUserId }: { targetUserId: string }) {
  const {
    callState,
    localStream,
    remoteStream,
    startCall,
    acceptCall,
    rejectCall,
    endCall,
    toggleAudio,
    toggleVideo,
    isAudioMuted,
    isVideoOff,
  } = useWebRTC();

  const localVideoRef = useRef<HTMLVideoElement>(null);
  const remoteVideoRef = useRef<HTMLVideoElement>(null);

  useEffect(() => {
    if (localVideoRef.current && localStream) {
      localVideoRef.current.srcObject = localStream;
    }
  }, [localStream]);

  useEffect(() => {
    if (remoteVideoRef.current && remoteStream) {
      remoteVideoRef.current.srcObject = remoteStream;
    }
  }, [remoteStream]);

  if (callState === 'incoming') {
    return (
      <div>
        <p>Incoming call...</p>
        <button onClick={acceptCall}>Accept</button>
        <button onClick={rejectCall}>Decline</button>
      </div>
    );
  }

  return (
    <div>
      {callState === 'idle' && (
        <button onClick={() => startCall(targetUserId, 'video')}>Start Video Call</button>
      )}

      {(callState === 'calling' || callState === 'connecting' || callState === 'active') && (
        <div>
          <video ref={remoteVideoRef} autoPlay playsInline style={{ width: '100%' }} />
          <video ref={localVideoRef} autoPlay playsInline muted style={{ width: 120, position: 'absolute', bottom: 16, right: 16 }} />

          <div>
            <button onClick={toggleAudio}>{isAudioMuted ? 'Unmute' : 'Mute'}</button>
            <button onClick={toggleVideo}>{isVideoOff ? 'Show Camera' : 'Hide Camera'}</button>
            <button onClick={endCall}>End Call</button>
          </div>
        </div>
      )}
    </div>
  );
}

Audio-only call:

await startCall(targetUserId, 'audio');

Screen share (add to an active call):

await startScreenShare(); // adds screen track to the existing RTCPeerConnection
stopScreenShare();        // removes the screen track

Data channel (low-latency bidirectional data):

const { startCall, dataChannel, sendData } = useWebRTC();

// Start a data-only call
await startCall(targetUserId, 'data');

// Send data once the channel is open
useEffect(() => {
  if (!dataChannel) return;
  dataChannel.onopen = () => sendData('hello peer');
  dataChannel.onmessage = (e) => console.log('received:', e.data);
}, [dataChannel]);

ICE / STUN configuration:

The server automatically emits ICE server config on connect (webrtc:config). The hook picks it up automatically — no manual ICE configuration needed. To add a TURN server for production NAT traversal, set WEBRTC_TURN_URLS, WEBRTC_TURN_USERNAME, and WEBRTC_TURN_CREDENTIAL on the Hive-Socket server.


Reconnect and missed events

The provider automatically tracks the last received event ID. On reconnect, it emits reconnect:sync so the server replays any events you missed (up to 5 minutes). No action required — it's built in.


Complete example — Workspace project chat

// app/(authed)/projects/[id]/chat/page.tsx
'use client';

import {
  useHiveSocket,
  useMessages,
  useNotifications,
} from '@smarthivelabs-devs/hive-socket-react';
import { useState } from 'react';

export default function ProjectChatPage({ params }: { params: { id: string } }) {
  const roomId = `project:${params.id}:general`;
  const { status, sendMessage } = useHiveSocket();
  const messages = useMessages(roomId);
  const { unreadCount } = useNotifications();
  const [text, setText] = useState('');

  const send = () => {
    if (!text.trim() || status !== 'connected') return;
    sendMessage(roomId, 'chat.message', { text });
    setText('');
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>

      <div style={{ padding: 12, borderBottom: '1px solid #eee', display: 'flex', justifyContent: 'space-between' }}>
        <span style={{ color: status === 'connected' ? 'green' : 'gray' }}>
          {status}
        </span>
        <span>🔔 {unreadCount}</span>
      </div>

      <div style={{ flex: 1, overflowY: 'auto', padding: 16 }}>
        {messages.map((msg) => (
          <div key={msg._eid} style={{ marginBottom: 8 }}>
            <strong>{msg.senderId}</strong>: {msg.payload.text as string}
          </div>
        ))}
      </div>

      <div style={{ display: 'flex', padding: 12, borderTop: '1px solid #eee' }}>
        <input
          style={{ flex: 1, marginRight: 8 }}
          value={text}
          onChange={(e) => setText(e.target.value)}
          onKeyDown={(e) => e.key === 'Enter' && send()}
          placeholder="Message..."
        />
        <button onClick={send} disabled={status !== 'connected'}>
          Send
        </button>
      </div>

    </div>
  );
}

Room naming

Rooms are scoped per project by the server — you only pass the room name, never the project ID.

| Room pattern | Use case | |-------------|----------| | project:{id}:general | Project-wide chat | | project:{id}:thread:{threadId} | Specific thread | | course:{courseId} | Course-level updates | | event:{eventId} | Live voting session |

Each user is automatically in their own user:{userId} room on connect — notifications sent by the backend always arrive without needing an explicit joinRoom.


Types

import type {
  HiveMessage,
  HiveHistoryMessage,
  HiveNotification,
  HivePresenceChange,
  HiveSocketError,
  HiveSocketProviderProps,
  NotificationType,
  PresenceStatus,
  ConnectionStatus,
  UseHiveSocketReturn,
  PresenceState,
  UseNotificationsReturn,
  // WebRTC
  WebRtcCallType,
  WebRtcCallState,
  HiveWebRtcCall,
  HiveIceServer,
  HiveWebRtcConfig,
  UseWebRTCReturn,
} from '@smarthivelabs-devs/hive-socket-react';

| Type | Description | |------|-------------| | HiveMessage | Real-time message from a room | | HiveHistoryMessage | Message row from history fetch | | HiveNotification | User notification (real-time or pending) | | HivePresenceChange | Presence status change event | | HiveSocketError | Server error payload | | ConnectionStatus | 'connecting' \| 'connected' \| 'disconnected' | | PresenceStatus | 'online' \| 'away' \| 'offline' | | NotificationType | 'info' \| 'success' \| 'warning' \| 'error' \| 'message' | | WebRtcCallType | 'audio' \| 'video' \| 'screen' \| 'data' | | WebRtcCallState | 'idle' \| 'calling' \| 'incoming' \| 'connecting' \| 'active' \| 'ended' | | HiveWebRtcCall | Active call metadata (callId, callerId, targetId, callType) | | HiveIceServer | ICE server config (urls, optional username/credential) | | HiveWebRtcConfig | ICE config emitted by server on connect |


Security

How the token is sent and validated

When you pass token to HiveSocketProvider, the SDK sends it in the Socket.IO handshake auth object:

io(url, { auth: { token } })

The server receives it during the connection handshake — before any events are processed. It verifies the JWT using your SmartHive Auth project secret (AUTH_SECRET). If verification fails the connection is immediately rejected with UNAUTHORIZED and no events are ever processed.

After a successful verify, the server extracts userId and projectId directly from the JWT claims. You never pass these yourself — they cannot be spoofed by the client.

What the server enforces

| Guarantee | How | |-----------|-----| | Token is valid and unexpired | JWT signature verified on every new connection | | User can only receive their own notifications | Server auto-joins {projectId}::user:{userId} — derived from the token, not from client input | | User can only join rooms in their project | Room joins are namespaced to the token's projectId server-side | | Stale tokens are rejected on reconnect | Every reconnect re-runs the full handshake verification |

What you must do

  • Pass the access token, not the refresh token. The access token is short-lived; the server will reject an expired one and disconnect cleanly.
  • Reconnect after a token refresh. The provider does this automatically — every time token changes, it tears down the old socket and opens a new connection with the new token.
  • Never pass the token in the URL (e.g. ?token=...). The SDK uses the auth handshake field so the token is not logged by proxies or stored in browser history.
  • Keep HiveSocketProvider inside your auth boundary. Only render it when the user is authenticated and a valid token is available. Pass '' or skip rendering entirely when the token is absent — the provider skips connecting when the token is falsy.

Token lifecycle example (Next.js)

// app/providers.tsx
'use client';
import { HiveSocketProvider } from '@smarthivelabs-devs/hive-socket-react';
import { useSession } from '@smarthivelabs-devs/auth-react'; // SmartHive Auth hook

export function Providers({ children }: { children: React.ReactNode }) {
  const { accessToken } = useSession(); // null before auth, auto-refreshed by the SDK

  // Provider skips connecting when token is falsy — safe to render unconditionally
  return (
    <HiveSocketProvider url="https://socket.smarthivelabs.dev" token={accessToken ?? ''}>
      {children}
    </HiveSocketProvider>
  );
}

When SmartHive Auth silently refreshes the access token, accessToken changes → provider automatically reconnects with the new token. No manual handling needed.


Troubleshooting

Provider must be a Client Component Add 'use client' to the component that renders HiveSocketProvider. In Next.js App Router, create a providers.tsx wrapper — do not add 'use client' to layout.tsx.

Connection immediately fails with UNAUTHORIZED

  • The token must be a valid JWT from SmartHive Auth
  • Pass the raw token — no Bearer prefix
  • The provider reconnects automatically when you pass a new token

CORS error in browser Your frontend origin must be in CORS_ORIGINS on the Hive-Socket server. In development: http://localhost:3000.

Messages missing after page refresh History is loaded from the database on every mount via useMessages. If you see an empty list, check that the connection status reaches 'connected' — the hook waits for connection before joining and fetching.