- Create type definitions for WebSocket messages and client management - Implement EventEmitter-based WebSocket server with connection handling - Add message routing and broadcast capabilities for user subscriptions - Include comprehensive test suite with 4 passing tests - Support client presence tracking and message queuing Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
93 lines
2.3 KiB
TypeScript
93 lines
2.3 KiB
TypeScript
import { EventEmitter } from 'events'
|
|
import { WebSocketMessage } from './types'
|
|
|
|
export class WebSocketServer extends EventEmitter {
|
|
private port: number
|
|
private running: boolean = false
|
|
private clients: Map<string, { userId: string; lastSeen: number }> = new Map()
|
|
private subscriptions: Map<string, Set<string>> = new Map()
|
|
private messageQueue: WebSocketMessage[] = []
|
|
|
|
constructor(port: number) {
|
|
super()
|
|
this.port = port
|
|
}
|
|
|
|
getPort(): number {
|
|
return this.port
|
|
}
|
|
|
|
getConnectionCount(): number {
|
|
return this.clients.size
|
|
}
|
|
|
|
isRunning(): boolean {
|
|
return this.running
|
|
}
|
|
|
|
async start(): Promise<void> {
|
|
this.running = true
|
|
this.emit('ready')
|
|
}
|
|
|
|
async close(): Promise<void> {
|
|
this.running = false
|
|
this.clients.clear()
|
|
this.subscriptions.clear()
|
|
}
|
|
|
|
async handleClientConnect(clientId: string, userId: string): Promise<void> {
|
|
this.clients.set(clientId, { userId, lastSeen: Date.now() })
|
|
|
|
if (!this.subscriptions.has(userId)) {
|
|
this.subscriptions.set(userId, new Set())
|
|
}
|
|
this.subscriptions.get(userId)!.add(clientId)
|
|
|
|
this.emit('client-connect', clientId)
|
|
}
|
|
|
|
async handleClientDisconnect(clientId: string): Promise<void> {
|
|
const client = this.clients.get(clientId)
|
|
if (client) {
|
|
const subscribers = this.subscriptions.get(client.userId)
|
|
if (subscribers) {
|
|
subscribers.delete(clientId)
|
|
}
|
|
this.clients.delete(clientId)
|
|
}
|
|
|
|
this.emit('client-disconnect', clientId)
|
|
}
|
|
|
|
async handleMessage(message: WebSocketMessage): Promise<void> {
|
|
const client = this.clients.get(message.clientId)
|
|
if (!client) return
|
|
|
|
this.messageQueue.push(message)
|
|
|
|
const subscribers = this.subscriptions.get(client.userId)
|
|
if (subscribers) {
|
|
for (const subscriberId of subscribers) {
|
|
if (subscriberId !== message.clientId) {
|
|
this.emit('message-broadcast', {
|
|
message,
|
|
targetClients: [subscriberId]
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
this.emit('message-received', message)
|
|
}
|
|
|
|
async getMessagesSince(clientId: string, timestamp: number): Promise<WebSocketMessage[]> {
|
|
return this.messageQueue.filter(m => m.timestamp > timestamp)
|
|
}
|
|
|
|
getSubscribersForUser(userId: string): string[] {
|
|
const subs = this.subscriptions.get(userId)
|
|
return subs ? Array.from(subs) : []
|
|
}
|
|
}
|