feat: create WebSocket client and real-time sync manager
This commit is contained in:
119
lib/websocket/client.ts
Normal file
119
lib/websocket/client.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { WebSocketMessage, WebSocketMessageType } from './types'
|
||||
|
||||
export class WebSocketClient extends EventEmitter {
|
||||
private url: string
|
||||
private clientId: string = `client-${Math.random().toString(36).substr(2, 9)}`
|
||||
private userId: string | null = null
|
||||
private connected: boolean = false
|
||||
private messageQueue: WebSocketMessage[] = []
|
||||
private ws: WebSocket | null = null
|
||||
private reconnectAttempts: number = 0
|
||||
private maxReconnectAttempts: number = 5
|
||||
private reconnectDelay: number = 1000
|
||||
|
||||
constructor(url: string) {
|
||||
super()
|
||||
this.url = url
|
||||
}
|
||||
|
||||
getClientId(): string {
|
||||
return this.clientId
|
||||
}
|
||||
|
||||
isConnected(): boolean {
|
||||
return this.connected && this.ws !== null && this.ws.readyState === WebSocket.OPEN
|
||||
}
|
||||
|
||||
getQueueLength(): number {
|
||||
return this.messageQueue.length
|
||||
}
|
||||
|
||||
async connect(userId: string): Promise<void> {
|
||||
this.userId = userId
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
this.ws = new WebSocket(this.url)
|
||||
|
||||
this.ws.onopen = () => {
|
||||
this.connected = true
|
||||
this.reconnectAttempts = 0
|
||||
this.emit('connected')
|
||||
this.flushMessageQueue()
|
||||
resolve()
|
||||
}
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const message: WebSocketMessage = JSON.parse(event.data)
|
||||
this.emit(message.type, message.payload)
|
||||
this.emit('message', message)
|
||||
} catch (error) {
|
||||
console.error('Failed to parse message:', error)
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error)
|
||||
this.emit('error', error)
|
||||
reject(error)
|
||||
}
|
||||
|
||||
this.ws.onclose = () => {
|
||||
this.connected = false
|
||||
this.emit('disconnected')
|
||||
this.attemptReconnect()
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
send(type: WebSocketMessageType, payload: Record<string, any>): void {
|
||||
const message: WebSocketMessage = {
|
||||
type,
|
||||
payload,
|
||||
timestamp: Date.now(),
|
||||
clientId: this.clientId
|
||||
}
|
||||
|
||||
if (this.isConnected() && this.ws) {
|
||||
this.ws.send(JSON.stringify(message))
|
||||
} else {
|
||||
this.messageQueue.push(message)
|
||||
}
|
||||
}
|
||||
|
||||
private flushMessageQueue(): void {
|
||||
while (this.messageQueue.length > 0) {
|
||||
const message = this.messageQueue.shift()
|
||||
if (message && this.ws) {
|
||||
this.ws.send(JSON.stringify(message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private attemptReconnect(): void {
|
||||
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++
|
||||
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1)
|
||||
setTimeout(() => {
|
||||
if (this.userId) {
|
||||
this.connect(this.userId).catch(() => {
|
||||
// Retry will happen in onclose
|
||||
})
|
||||
}
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
if (this.ws) {
|
||||
this.ws.close()
|
||||
}
|
||||
this.connected = false
|
||||
this.messageQueue = []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user