122 lines
2.7 KiB
TypeScript
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
|
|
}
|
|
}
|
|
}
|