- 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> |
||
|---|---|---|
| .. | ||
| src | ||
| .eslintrc.json | ||
| package.json | ||
| README.md | ||
| TESTING.md | ||
| tsconfig.eslint.json | ||
| tsconfig.json | ||
@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 URLtoken(string, optional) - JWT authentication tokenreconnection(boolean, default: true) - Enable auto-reconnectionreconnectionAttempts(number, default: Infinity) - Max reconnection attemptsreconnectionDelay(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 instanceclient(WebSocketClient | null) - Client wrapper instanceconnected(boolean) - Connection statusconnecting(boolean) - Connection in progresserror(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 itemsloading(boolean) - Request loading statesubscribed(boolean) - Subscription statussubscribe()- Subscribe to menu updatesunsubscribe()- Unsubscribe from menu updatesrequest()- 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 updatedgoal:completed- Goal completed
Returns:
goals(Goal[]) - Active goalsloading(boolean) - Request loading statesubscribed(boolean) - Subscription statussubscribe()- Subscribe to goal updatesunsubscribe()- Unsubscribe from goal updatesrequest()- 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 tipsubscribed(boolean) - Subscription statussubscribe()- Subscribe to tip notificationsunsubscribe()- Unsubscribe from tip notificationsclearTips()- 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 receivedchatbot:error- Error processing message
Persona Routing:
@quinn,@quin→ Quinn (performer persona)@quinnbot,@quinbot,@qbot→ QBot (assistant persona)
Returns:
messages(ChatMessage[]) - Chat historysubscribed(boolean) - Subscription statussubscribe()- Subscribe to chatbot eventsunsubscribe()- Unsubscribe from chatbot eventssendMessage(message)- Send a message to chatbotclearMessages()- 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:
- WebSocketClient - Core client with auto-reconnection (exponential backoff)
- React Hooks - State management and event handling
- Type Safety - Full TypeScript definitions for all events
- 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