mythic-bastionland/app/components/WsDemo.tsx

94 lines
3.1 KiB
TypeScript

"use client"
import { useEffect, useRef, useState } from "react"
export default function WsDemo() {
const [connected, setConnected] = useState(false)
const [messages, setMessages] = useState<string[]>([])
const [input, setInput] = useState("")
const wsRef = useRef<WebSocket | null>(null)
useEffect(() => {
const protocol = window.location.protocol === "https:" ? "wss" : "ws"
const url = `${protocol}://${window.location.host}/api/ws`
const existing = (window as any).__ws as WebSocket | undefined
const ws = existing && (existing.readyState === WebSocket.OPEN || existing.readyState === WebSocket.CONNECTING)
? existing
: new WebSocket(url)
;(window as any).__ws = ws
wsRef.current = ws
const onOpen = (event: Event) => {
console.log("onOpen", event)
setConnected(true)
}
const onClose = (event: Event) => {
console.log("onClose", event)
setConnected(false)
}
const onError = (event: Event) => {
console.log("onError", event)
setConnected(false)
}
const onMessage = (event: MessageEvent) => {
console.log("onMessage", event)
try {
const payload = JSON.parse(event.data)
if (payload?.type === "server:hello" || payload?.type === "server:pong") {
setMessages((prev) => [...prev, payload.message])
}
} catch {}
}
ws.addEventListener("open", onOpen)
ws.addEventListener("close", onClose)
ws.addEventListener("error", onError)
ws.addEventListener("message", onMessage)
const beforeUnload = () => { try { ws.close() } catch {} }
window.addEventListener("beforeunload", beforeUnload)
return () => {
console.log("unmounting")
ws.removeEventListener("open", onOpen)
ws.removeEventListener("close", onClose)
ws.removeEventListener("error", onError)
ws.removeEventListener("message", onMessage)
window.removeEventListener("beforeunload", beforeUnload)
}
}, [])
const sendPing = () => {
const msg = input || "ping"
wsRef.current?.send(JSON.stringify({ type: "client:ping", message: msg }))
setInput("")
}
return (
<div className="w-full max-w-xl rounded-md border border-neutral-300 dark:border-neutral-800 p-4 space-y-3">
<div className="flex items-center gap-2">
<span className={`inline-block w-2.5 h-2.5 rounded-full ${connected ? "bg-emerald-500" : "bg-rose-500"}`} />
<span className="font-semibold">WebSocket</span>
<span className="opacity-70">{connected ? "connected" : "disconnected"}</span>
</div>
<div className="flex gap-2">
<input
className="flex-1 rounded-md border border-neutral-300 dark:border-neutral-800 bg-transparent px-3 py-2"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type a message"
/>
<button className="rounded-md border px-3 py-2" onClick={sendPing}>Send ping</button>
</div>
<ul className="space-y-1 text-sm">
{messages.map((m, i) => (
<li key={i}>{m}</li>
))}
</ul>
</div>
)
}