Skip to content

Commit c57f2ab

Browse files
committed
feat: implement WebSocket support with new SimpleChat component and update environment variables
1 parent 350bd04 commit c57f2ab

File tree

9 files changed

+170
-53
lines changed

9 files changed

+170
-53
lines changed

.env

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
SECRET=km12poik3mokpaxnjcojsandfoj1nbjt
22
DATABASE_URL="file:../demo.db"
3-
NEXT_PUBLIC_URL=http://localhost:3000
4-
NEXT_PUBLIC_WEBSOCKET_URL=ws://localhost:3000/api/ws
3+
NEXT_PUBLIC_URL=http://localhost:3000

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"start": "prisma migrate deploy && next start",
1010
"lint": "next lint",
1111
"prisma": "prisma migrate dev --name init && prisma generate",
12-
"postinstall": "prisma generate"
12+
"postinstall": "prisma generate && next-ws patch"
1313
},
1414
"dependencies": {
1515
"@elysiajs/eden": "1.3.2",
@@ -18,14 +18,17 @@
1818
"elysia": "1.3.6",
1919
"jose": "6.0.12",
2020
"next": "15.4.4",
21+
"next-ws": "^2.0.12",
2122
"react": "19.1.0",
22-
"react-dom": "19.1.0"
23+
"react-dom": "19.1.0",
24+
"ws": "^8.18.3"
2325
},
2426
"devDependencies": {
2527
"@tailwindcss/postcss": "^4.1.11",
2628
"@types/node": "24.1.0",
2729
"@types/react": "19.1.8",
2830
"@types/react-dom": "19.1.6",
31+
"@types/ws": "^8.5.10",
2932
"eslint": "9.17.0",
3033
"eslint-config-next": "15.4.4",
3134
"postcss": "8.5.6",

src/app/(main)/dashboard/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import ChatComponent from "@/components/chat-component";
3+
import ChatComponent from "@/components/simple-component";
44
import { useMeQuery } from "@/hooks/user-hook";
55
import Link from "next/link";
66

src/app/api/ws/route.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { WebSocket } from "ws";
2+
3+
const clients = new Set<WebSocket>();
4+
5+
export function SOCKET(
6+
client: WebSocket,
7+
request: import("http").IncomingMessage,
8+
server: import("ws").WebSocketServer,
9+
) {
10+
console.log("Client connected");
11+
clients.add(client);
12+
13+
// Send welcome message
14+
client.send(
15+
JSON.stringify({
16+
type: "welcome",
17+
message: "Connected to WebSocket server",
18+
}),
19+
);
20+
21+
client.on("message", (data) => {
22+
try {
23+
const message = data.toString();
24+
console.log("Received:", message);
25+
26+
// Broadcast to all connected clients
27+
const response = JSON.stringify({
28+
type: "message",
29+
data: message,
30+
timestamp: Date.now(),
31+
});
32+
33+
clients.forEach((c) => {
34+
if (c.readyState === c.OPEN && c !== client) {
35+
c.send(response);
36+
}
37+
});
38+
39+
// Echo back to sender
40+
client.send(
41+
JSON.stringify({
42+
type: "echo",
43+
data: message,
44+
timestamp: Date.now(),
45+
}),
46+
);
47+
} catch (error) {
48+
console.error("Message handling error:", error);
49+
}
50+
});
51+
52+
client.on("close", () => {
53+
console.log("Client disconnected");
54+
clients.delete(client);
55+
});
56+
57+
client.on("error", (error) => {
58+
console.error("WebSocket error:", error);
59+
clients.delete(client);
60+
});
61+
}

src/components/chat-component.tsx

Lines changed: 0 additions & 19 deletions
This file was deleted.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"use client";
2+
3+
import { useWebSocket } from "@/hooks/websocket-hook";
4+
import { useState } from "react";
5+
6+
export default function SimpleChat() {
7+
const { isConnected, messages, sendMessage } = useWebSocket();
8+
const [input, setInput] = useState("");
9+
10+
const handleSend = () => {
11+
if (input.trim()) {
12+
sendMessage(input);
13+
setInput("");
14+
}
15+
};
16+
17+
return (
18+
<div className="mx-auto max-w-md p-4">
19+
<div className="mb-4 rounded-lg bg-gray-800 p-2">
20+
<span
21+
className={`text-sm ${isConnected ? "text-green-400" : "text-red-400"}`}
22+
>
23+
{isConnected ? "● Connected" : "● Disconnected"}
24+
</span>
25+
</div>
26+
27+
<div className="mb-4 h-64 overflow-y-auto rounded-lg border border-gray-600 bg-gray-900 p-2">
28+
{messages.length === 0 ? (
29+
<div className="text-sm text-gray-500">No messages yet...</div>
30+
) : (
31+
messages.map((msg, i) => (
32+
<div key={i} className="mb-2 text-sm">
33+
{msg}
34+
</div>
35+
))
36+
)}
37+
</div>
38+
39+
<div className="flex gap-2">
40+
<input
41+
type="text"
42+
value={input}
43+
onChange={(e) => setInput(e.target.value)}
44+
onKeyPress={(e) => e.key === "Enter" && handleSend()}
45+
className="flex-1 rounded border border-gray-600 bg-gray-800 p-2 text-white"
46+
placeholder="Type a message..."
47+
disabled={!isConnected}
48+
/>
49+
<button
50+
onClick={handleSend}
51+
disabled={!isConnected || !input.trim()}
52+
className="rounded bg-blue-500 px-4 py-2 text-white transition-colors hover:bg-blue-600 disabled:bg-gray-600"
53+
>
54+
Send
55+
</button>
56+
</div>
57+
</div>
58+
);
59+
}

src/hooks/websocket-hook.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,58 @@
11
import { useEffect, useRef, useState } from "react";
22

3+
interface WebSocketMessage {
4+
type: "welcome" | "message" | "echo";
5+
data?: string;
6+
message?: string;
7+
timestamp?: number;
8+
}
9+
310
export function useWebSocket() {
411
const [isConnected, setIsConnected] = useState(false);
512
const [messages, setMessages] = useState<string[]>([]);
613
const ws = useRef<WebSocket | null>(null);
714

815
useEffect(() => {
9-
ws.current = new WebSocket(process.env.NEXT_PUBLIC_WEBSOCKET_URL);
16+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
17+
const wsUrl = `${protocol}//${window.location.host}/api/ws`;
18+
19+
ws.current = new WebSocket(wsUrl);
20+
21+
ws.current.onopen = () => {
22+
console.log("WebSocket connected");
23+
setIsConnected(true);
24+
};
25+
26+
ws.current.onclose = () => {
27+
console.log("WebSocket disconnected");
28+
setIsConnected(false);
29+
};
30+
31+
ws.current.onerror = (error) => {
32+
console.error("WebSocket error:", error);
33+
setIsConnected(false);
34+
};
1035

11-
ws.current.onopen = () => setIsConnected(true);
12-
ws.current.onclose = () => setIsConnected(false);
1336
ws.current.onmessage = (event) => {
14-
setMessages((prev) => [...prev, event.data]);
37+
try {
38+
const parsed: WebSocketMessage = JSON.parse(event.data);
39+
40+
if (parsed.type === "welcome") {
41+
setMessages((prev) => [...prev, `System: ${parsed.message}`]);
42+
} else if (parsed.type === "message") {
43+
setMessages((prev) => [...prev, `User: ${parsed.data}`]);
44+
} else if (parsed.type === "echo") {
45+
setMessages((prev) => [...prev, `Echo: ${parsed.data}`]);
46+
}
47+
} catch {
48+
// Fallback for non-JSON messages
49+
setMessages((prev) => [...prev, event.data]);
50+
}
1551
};
1652

17-
return () => ws.current?.close();
53+
return () => {
54+
ws.current?.close();
55+
};
1856
}, []);
1957

2058
const sendMessage = (message: string) => {

src/lib/types/env.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ declare namespace NodeJS {
22
export interface ProcessEnv {
33
SECRET: string;
44
NEXT_PUBLIC_URL: string;
5-
NEXT_PUBLIC_WEBSOCKET_URL: string;
65
DATABASE_URL: string;
76
}
87
}

src/server/websocket/index.ts

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)