Get websockets working
This commit is contained in:
parent
bd14edadf7
commit
0299b5bb43
23
app/api/ws/route.ts
Normal file
23
app/api/ws/route.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import type { NextRequest } from "next/server"
|
||||
import type { WebSocket, WebSocketServer } from "ws"
|
||||
|
||||
export function UPGRADE(client: WebSocket, server: WebSocketServer, _req: NextRequest) {
|
||||
client.send(JSON.stringify({ type: "server:hello", message: "Connected via next-ws" }))
|
||||
|
||||
client.on("message", (data) => {
|
||||
let payload: any
|
||||
try {
|
||||
payload = JSON.parse(data.toString())
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
if (payload?.type === "client:ping") {
|
||||
const message = typeof payload.message === "string" ? payload.message : ""
|
||||
const response = { type: "server:pong", message: `pong: ${message}`.trim() }
|
||||
server.clients.forEach((peer) => {
|
||||
if (peer.readyState === client.OPEN) peer.send(JSON.stringify(response))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
93
app/components/WsDemo.tsx
Normal file
93
app/components/WsDemo.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
"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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import Image from "next/image";
|
||||
import WsDemo from "./components/WsDemo";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
@ -12,6 +13,7 @@ export default function Home() {
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<WsDemo />
|
||||
<ol className="font-mono list-inside list-decimal text-sm/6 text-center sm:text-left">
|
||||
<li className="mb-2 tracking-[-.01em]">
|
||||
Get started by editing{" "}
|
||||
|
||||
1249
package-lock.json
generated
1249
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
14
package.json
14
package.json
@ -7,20 +7,24 @@
|
||||
"build": "next build --turbopack",
|
||||
"start": "next start",
|
||||
"lint": "biome check",
|
||||
"format": "biome format --write"
|
||||
"format": "biome format --write",
|
||||
"prepare": "next-ws patch"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^15.5.4",
|
||||
"next-ws": "^2.1.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"next": "15.5.4"
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5",
|
||||
"@biomejs/biome": "2.2.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@types/ws": "^8.18.1",
|
||||
"tailwindcss": "^4",
|
||||
"@biomejs/biome": "2.2.0"
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user