Files
cultivation-world-simulator/web/src/services/gameGateway.ts
2025-11-21 01:38:41 +08:00

122 lines
2.7 KiB
TypeScript

import type { TickPayload } from '../types/game'
export interface GatewayHandlers {
onTick?: (payload: TickPayload) => void
onStatusChange?: (connected: boolean) => void
onError?: (error: unknown) => void
}
export interface GatewayOptions {
reconnect?: boolean
url?: string
baseDelay?: number
maxDelay?: number
}
export function createGameGateway(
handlers: GatewayHandlers,
options: GatewayOptions = {}
) {
const reconnectEnabled = options.reconnect !== false
const baseDelay = options.baseDelay ?? 1000
const maxDelay = options.maxDelay ?? 8000
let ws: WebSocket | null = null
let reconnectTimer: number | null = null
let reconnectAttempts = 0
let manuallyClosed = false
function getUrl() {
if (options.url) return options.url
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
return `${protocol}//${window.location.host}/ws`
}
function clearReconnectTimer() {
if (reconnectTimer != null) {
window.clearTimeout(reconnectTimer)
reconnectTimer = null
}
}
function cleanupSocket() {
if (!ws) return
ws.onopen = null
ws.onmessage = null
ws.onerror = null
ws.onclose = null
ws = null
}
function scheduleReconnect() {
if (!reconnectEnabled || manuallyClosed) return
clearReconnectTimer()
const delay = Math.min(maxDelay, baseDelay * 2 ** reconnectAttempts)
reconnectAttempts += 1
reconnectTimer = window.setTimeout(() => {
connect()
}, delay)
}
function handleMessage(event: MessageEvent) {
try {
const data = JSON.parse(event.data)
if (data?.type === 'tick') {
handlers.onTick?.(data as TickPayload)
}
} catch (error) {
handlers.onError?.(error)
}
}
function handleOpen() {
reconnectAttempts = 0
handlers.onStatusChange?.(true)
}
function handleClose() {
handlers.onStatusChange?.(false)
cleanupSocket()
scheduleReconnect()
}
function handleError(error: Event) {
handlers.onError?.(error)
}
function connect() {
manuallyClosed = false
clearReconnectTimer()
cleanupSocket()
try {
ws = new WebSocket(getUrl())
ws.onopen = handleOpen
ws.onmessage = handleMessage
ws.onerror = handleError
ws.onclose = handleClose
} catch (error) {
handlers.onError?.(error)
scheduleReconnect()
}
}
function disconnect() {
manuallyClosed = true
clearReconnectTimer()
if (ws && ws.readyState === WebSocket.OPEN) {
ws.close()
} else {
cleanupSocket()
}
}
return {
connect,
disconnect,
get readyState() {
return ws?.readyState ?? WebSocket.CLOSED
}
}
}