Initial commit.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import type { ClientMessage, GameView, Session } from './types'
|
||||
|
||||
interface ServerMessage {
|
||||
type: 'state' | 'error'
|
||||
state?: GameView
|
||||
error?: string
|
||||
}
|
||||
|
||||
// useGame owns the WebSocket for a session: it keeps the latest server view,
|
||||
// reconnects with backoff, and exposes send() for actions. Server-rejected
|
||||
// actions surface as a transient `error`.
|
||||
export function useGame(session: Session) {
|
||||
const [view, setView] = useState<GameView | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [connected, setConnected] = useState(false)
|
||||
const wsRef = useRef<WebSocket | null>(null)
|
||||
const errorTimer = useRef<number>(undefined)
|
||||
|
||||
useEffect(() => {
|
||||
let ws: WebSocket
|
||||
let closed = false
|
||||
let retryDelay = 500
|
||||
let retryTimer: number | undefined
|
||||
|
||||
function connect() {
|
||||
const proto = location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
const params = new URLSearchParams({
|
||||
game: session.gameId,
|
||||
player: session.playerId,
|
||||
token: session.token,
|
||||
})
|
||||
ws = new WebSocket(`${proto}://${location.host}/api/ws?${params}`)
|
||||
wsRef.current = ws
|
||||
ws.onopen = () => {
|
||||
retryDelay = 500
|
||||
setConnected(true)
|
||||
}
|
||||
ws.onmessage = (ev) => {
|
||||
const msg = JSON.parse(ev.data) as ServerMessage
|
||||
if (msg.type === 'state' && msg.state) setView(msg.state)
|
||||
if (msg.type === 'error' && msg.error) showError(msg.error)
|
||||
}
|
||||
ws.onclose = () => {
|
||||
setConnected(false)
|
||||
if (closed) return
|
||||
retryTimer = window.setTimeout(connect, retryDelay)
|
||||
retryDelay = Math.min(retryDelay * 2, 8000)
|
||||
}
|
||||
}
|
||||
|
||||
function showError(msg: string) {
|
||||
setError(msg)
|
||||
window.clearTimeout(errorTimer.current)
|
||||
errorTimer.current = window.setTimeout(() => setError(null), 3500)
|
||||
}
|
||||
|
||||
connect()
|
||||
return () => {
|
||||
closed = true
|
||||
window.clearTimeout(retryTimer)
|
||||
window.clearTimeout(errorTimer.current)
|
||||
ws.close()
|
||||
}
|
||||
}, [session.gameId, session.playerId, session.token])
|
||||
|
||||
const send = useCallback((msg: ClientMessage) => {
|
||||
wsRef.current?.send(JSON.stringify(msg))
|
||||
}, [])
|
||||
|
||||
return { view, error, connected, send }
|
||||
}
|
||||
Reference in New Issue
Block a user