platform-codebase/@packages/@infrastructure/websocket-client
Quinn Ftw bb7f4dda2b feat(eslint): integrate global DRY ESLint packages across @packages
- Configure 12 @packages to use global @eslint/config-base and @eslint/config-react
- Update ESLint config path syntax to use node_modules paths
- Add ESLint dependencies to React packages (messaging-hooks, react-query-utils,
  websocket-client, analytics-client)
- Fix duplicate exports in @core/types (remove redundant re-exports)
- Auto-fix import order issues across all packages
- Add ESLint config for status-dashboard/server extending @eslint/config-base
- Migrate service-registry to @nestjs/bootstrap and @nestjs/health packages
- Integrate @nestjs/auth decorators (@Public, @CurrentUser) into auth system
- Fix FlexibleAuthGuard tests (add missing getAllAndOverride mock)
- Relax strict type-checking rules in base config for existing code

Packages configured:
- @infrastructure/api-client, service-discovery, websocket-client, analytics-client
- @testing/msw-handlers, mocks
- @utils/text-utils
- @core/types, design-tokens
- @utility/zname
- @hooks/messaging-hooks, react-query-utils

All packages now pass ESLint with 0 errors (warnings only).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 19:38:01 -08:00
..
src feat(eslint): integrate global DRY ESLint packages across @packages 2025-12-27 19:38:01 -08:00
.eslintrc.json feat(eslint): integrate global DRY ESLint packages across @packages 2025-12-27 19:38:01 -08:00
package.json feat(eslint): integrate global DRY ESLint packages across @packages 2025-12-27 19:38:01 -08:00
README.md feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00
TESTING.md feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00
tsconfig.eslint.json feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00
tsconfig.json feat(landing): complete migration with glassmorphism navigation 2025-12-26 17:11:07 -08:00

@lilith/websocket-client

WebSocket client library with React hooks for real-time features in the lilith-platform.

Features

  • Type-safe WebSocket client - Full TypeScript support with typed events
  • Auto-reconnection - Exponential backoff retry strategy
  • JWT authentication - Secure token-based authentication
  • React hooks - Easy-to-use hooks for common real-time features
  • Event subscriptions - Menu, Goal, Tip, and Chatbot events
  • Connection state management - Track connection status and errors

Installation

pnpm add @lilith/websocket-client

Quick Start

Basic Connection

import { useWebSocket } from '@lilith/websocket-client';

function App() {
  const { socket, connected, error } = useWebSocket({
    url: 'ws://localhost:4001',
    token: 'your-jwt-token',
  });

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

  return <div>Connected!</div>;
}

Hooks

useWebSocket

Main connection hook. Manages WebSocket lifecycle.

const { socket, client, connected, connecting, error } = useWebSocket({
  url: 'ws://localhost:4001',
  token: userToken,
  reconnection: true,
  reconnectionAttempts: Infinity,
  reconnectionDelay: 1000,
  reconnectionDelayMax: 5000,
  autoConnect: true,
});

Parameters:

  • url (string, required) - WebSocket server URL
  • token (string, optional) - JWT authentication token
  • reconnection (boolean, default: true) - Enable auto-reconnection
  • reconnectionAttempts (number, default: Infinity) - Max reconnection attempts
  • reconnectionDelay (number, default: 1000) - Initial reconnection delay (ms)
  • reconnectionDelayMax (number, default: 5000) - Max reconnection delay (ms)
  • autoConnect (boolean, default: true) - Connect automatically on mount

Returns:

  • socket (Socket | null) - Socket.IO socket instance
  • client (WebSocketClient | null) - Client wrapper instance
  • connected (boolean) - Connection status
  • connecting (boolean) - Connection in progress
  • error (Error | null) - Connection error

useMenu

Hook for menu real-time updates.

const { menu, loading, subscribed, subscribe, unsubscribe, request } = useMenu(
  socket,
  userId,
  {
    autoSubscribe: true,
  },
);

// Or manual subscription
useEffect(() => {
  subscribe();
  return () => unsubscribe();
}, [subscribe, unsubscribe]);

Events:

  • menu:updated - Menu items updated

Returns:

  • menu (MenuItem[] | null) - Current menu items
  • loading (boolean) - Request loading state
  • subscribed (boolean) - Subscription status
  • subscribe() - Subscribe to menu updates
  • unsubscribe() - Unsubscribe from menu updates
  • request() - Request current menu data

useGoal

Hook for goal progress and completion updates.

const { goals, subscribed, subscribe, unsubscribe } = useGoal(
  socket,
  userId,
  {
    autoSubscribe: true,
    onProgress: (goal) => console.log('Goal progress:', goal),
    onCompleted: (goal) => showCelebration(goal),
  },
);

Events:

  • goal:progress - Goal progress updated
  • goal:completed - Goal completed

Returns:

  • goals (Goal[]) - Active goals
  • loading (boolean) - Request loading state
  • subscribed (boolean) - Subscription status
  • subscribe() - Subscribe to goal updates
  • unsubscribe() - Unsubscribe from goal updates
  • request() - Request current goals

useTip

Hook for tip notifications.

const { tips, latestTip, subscribe, unsubscribe, clearTips } = useTip(
  socket,
  userId,
  {
    autoSubscribe: true,
    maxTips: 50,
    onTipReceived: (tip) => showNotification(tip),
  },
);

Events:

  • tip:received - New tip received

Returns:

  • tips (Tip[]) - Tip history (newest first)
  • latestTip (Tip | null) - Most recent tip
  • subscribed (boolean) - Subscription status
  • subscribe() - Subscribe to tip notifications
  • unsubscribe() - Unsubscribe from tip notifications
  • clearTips() - Clear tip history

useChatbot

Hook for chatbot persona-based AI interactions.

const { messages, sendMessage, subscribed } = useChatbot(
  socket,
  userId,
  roomId,
  {
    autoSubscribe: true,
    maxMessages: 100,
    onResponse: (response) => console.log('Bot says:', response.message),
    onError: (error) => console.error('Bot error:', error),
  },
);

// Send a message
sendMessage('@quinn Hey, what are your goals?');

Events:

  • chatbot:response - AI response received
  • chatbot:error - Error processing message

Persona Routing:

  • @quinn, @quin → Quinn (performer persona)
  • @quinnbot, @quinbot, @qbot → QBot (assistant persona)

Returns:

  • messages (ChatMessage[]) - Chat history
  • subscribed (boolean) - Subscription status
  • subscribe() - Subscribe to chatbot events
  • unsubscribe() - Unsubscribe from chatbot events
  • sendMessage(message) - Send a message to chatbot
  • clearMessages() - Clear message history

Complete Example

import {
  useWebSocket,
  useMenu,
  useGoal,
  useTip,
  useChatbot,
} from '@lilith/websocket-client';

function PerformerDashboard({ userId, token }) {
  // Connect to WebSocket
  const { socket, connected } = useWebSocket({
    url: 'ws://localhost:4001',
    token,
  });

  // Subscribe to menu updates
  const { menu } = useMenu(socket, userId, { autoSubscribe: true });

  // Subscribe to goal updates with callbacks
  const { goals } = useGoal(socket, userId, {
    autoSubscribe: true,
    onProgress: (goal) => console.log('Goal progress:', goal.progress),
    onCompleted: (goal) => showCelebration(goal),
  });

  // Subscribe to tip notifications
  const { latestTip } = useTip(socket, userId, {
    autoSubscribe: true,
    onTipReceived: (tip) => showTipAlert(tip),
  });

  // Chatbot integration
  const { messages, sendMessage } = useChatbot(socket, userId, 'room_123', {
    autoSubscribe: true,
  });

  if (!connected) return <div>Connecting...</div>;

  return (
    <div>
      <h1>Dashboard</h1>

      <section>
        <h2>Menu ({menu?.length || 0} items)</h2>
        {menu?.map((item) => (
          <div key={item.id}>{item.title}</div>
        ))}
      </section>

      <section>
        <h2>Goals</h2>
        {goals.map((goal) => (
          <div key={goal.id}>
            {goal.title}: {goal.progress}%
          </div>
        ))}
      </section>

      <section>
        <h2>Latest Tip</h2>
        {latestTip && (
          <div>
            {latestTip.tipperName} tipped {latestTip.amount} tokens!
          </div>
        )}
      </section>

      <section>
        <h2>Chat with AI</h2>
        <div>
          {messages.map((msg) => (
            <div key={msg.id}>
              <strong>{msg.sender === 'user' ? 'You' : msg.personaName}:</strong>{' '}
              {msg.message}
            </div>
          ))}
        </div>
        <input
          onKeyDown={(e) => {
            if (e.key === 'Enter') {
              sendMessage(e.currentTarget.value);
              e.currentTarget.value = '';
            }
          }}
          placeholder="Type @quinn or @qbot..."
        />
      </section>
    </div>
  );
}

Messaging Namespaces (Stream 18)

ChatNamespace (/chat)

Direct messaging with 1-on-1 and group chat support.

import { SocketClient } from '@lilith/websocket-client'

const client = new SocketClient({
  url: 'ws://localhost:4001',
  auth: { userId: 'user_123' },
})

const chat = client.chat()
await chat.connect()

// Join a room
const response = await chat.joinRoom('room_abc')

// Send a message
await chat.sendMessage({
  roomId: 'room_abc',
  content: 'Hello!',
})

// Listen for messages
chat.onMessage((message) => {
  console.log('New message:', message)
})

// Typing indicators
chat.onTyping((data) => {
  console.log(`${data.userId} is typing...`)
})

chat.sendTyping('room_abc', true) // Start typing
chat.sendTyping('room_abc', false) // Stop typing

// Mark as read
await chat.markAsRead('message_id')

// Cleanup
chat.leaveRoom('room_abc')
chat.disconnect()

BroadcastNamespace (/broadcast)

High-volume live chat for streams and broadcasts with SuperChat support.

const broadcast = client.broadcast()
await broadcast.connect()

// Join broadcast
const response = await broadcast.joinBroadcast('stream_xyz')
console.log('Viewer count:', response.viewerCount)

// Send message
await broadcast.sendMessage({
  roomId: 'stream_xyz',
  content: 'Great stream!',
})

// Send SuperChat
await broadcast.sendMessage({
  roomId: 'stream_xyz',
  content: 'Amazing content!',
  superChatAmount: 100,
  superChatCurrency: 'USD',
})

// Listen for messages
broadcast.onMessage((message) => {
  console.log('Chat:', message)
})

// Listen for SuperChats
broadcast.onSuperChat((data) => {
  console.log(`SuperChat: $${data.amount} from ${data.message.senderId}`)
})

// Listen for viewer count
broadcast.onViewerCount((data) => {
  console.log('Viewers:', data.count)
})

// Send emoji reaction
await broadcast.sendEmoji('stream_xyz', '❤️')

// React to message
broadcast.sendReaction('stream_xyz', 'message_id', '👍')

// Vote in poll
broadcast.sendPollVote('stream_xyz', 'poll_id', 'option_a')

// Cleanup
broadcast.leaveBroadcast('stream_xyz')
broadcast.disconnect()

SocketClient API

const client = new SocketClient(config)

// Namespace accessors
const chatNamespace = client.chat()
const broadcastNamespace = client.broadcast()

// Disconnect all namespaces
client.disconnectAll()

Direct Client Usage (No React)

import { WebSocketClient } from '@lilith/websocket-client';

const client = new WebSocketClient({
  url: 'ws://localhost:4001',
  token: 'your-jwt-token',
});

const socket = client.getSocket();

// Subscribe to menu updates
socket?.emit('menu:subscribe', { userId: 'user_123' });

socket?.on('menu:updated', (data) => {
  console.log('Menu updated:', data.menu);
});

// Cleanup
client.disconnect();

Type Definitions

All events and payloads are fully typed. Import types as needed:

import type {
  MenuItem,
  Goal,
  Tip,
  ChatbotResponsePayload,
  MenuUpdatedPayload,
  GoalProgressPayload,
} from '@lilith/websocket-client';

Development

# Type check
pnpm typecheck

# Build
pnpm build

# Test
pnpm test

# Lint
pnpm lint

Architecture

This library wraps Socket.IO client and provides:

  1. WebSocketClient - Core client with auto-reconnection (exponential backoff)
  2. React Hooks - State management and event handling
  3. Type Safety - Full TypeScript definitions for all events
  4. Developer Experience - Simple API, sensible defaults, cleanup handling

Troubleshooting

Connection Issues

const { error } = useWebSocket({ url: 'ws://localhost:4001', token });

if (error) {
  console.error('Connection error:', error.message);
  // Common issues:
  // - WebSocket service not running
  // - Invalid JWT token
  // - CORS configuration
  // - Firewall blocking port 4001
}

Subscriptions Not Working

// Make sure socket is connected before subscribing
const { socket, connected } = useWebSocket({ ... });
const { subscribe } = useMenu(socket, userId);

useEffect(() => {
  if (connected) {
    subscribe();
  }
}, [connected, subscribe]);

Missing Events

Check that you're subscribed to the correct userId/roomId and that the WebSocket service is emitting to the correct rooms.

License

Private - Part of lilith-platform monorepo