Skip to content

Commit 318496f

Browse files
committed
fixing(websocket): optimize WebSocket connections in CanvasSheet component
1 parent d02eac5 commit 318496f

File tree

10 files changed

+329
-303
lines changed

10 files changed

+329
-303
lines changed

apps/collabydraw/components/CollaborationStartBtn.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export default function CollaborationStartBtn({ slug, participants }: { slug?: s
4242
</TooltipProvider>
4343
</div>
4444
</div>
45-
<Button type="button" onClick={() => setIsOpen(true)}
45+
<Button type="butt yu65tr4e3qson" onClick={() => setIsOpen(true)}
4646
className={cn("excalidraw-button collab-button relative w-auto py-3 px-4 rounded-md text-[.875rem] font-semibold shadow-none active:scale-[.98]", roomSlug ? "bg-[#0fb884] dark:bg-[#0fb884] hover:bg-[#0fb884]" : "bg-color-primary hover:bg-brand-hover active:bg-brand-active")}
4747
title="Live collaboration...">Share {roomSlug && participants && participants.length > 0 && (
4848
<div className="CollabButton-collaborators text-[.6rem] text-[#2b8a3e] bg-[#b2f2bb] font-bold font-assistant rounded-[50%] p-1 min-w-4 min-h-4 w-4 h-4 flex items-center justify-center absolute bottom-[-5px] right-[-5px]">{participants.length}</div>
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
export default function ScreenLoading() {
1+
export default function ScreenLoading({ content }: { content?: string }) {
22
return (
33
<div className="Loading h-dvh w-full flex items-center justify-center font-excalifont bg-island-bg-color text-loading-text-color text-xl tracking-wide">
4-
Loading Scenes...
4+
{content ? `${content}` : 'Loading Scenes...'}
55
</div>
66
)
77
}

apps/collabydraw/components/canvas/CanvasSheet.tsx

Lines changed: 129 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
"use client"
22

3-
import { useWebSocket } from "@/hooks/useWebSocket";
43
import { Game } from "@/draw/Game";
5-
import { BgFill, canvasBgLight, Shape, StrokeEdge, StrokeFill, StrokeStyle, StrokeWidth, ToolType } from "@/types/canvas";
6-
import { useCallback, useEffect, useRef, useState } from "react";
4+
import { BgFill, canvasBgLight, StrokeEdge, StrokeFill, StrokeStyle, StrokeWidth, ToolType } from "@/types/canvas";
5+
import React, { useCallback, useEffect, useRef, useState } from "react";
76
import { Scale } from "../Scale";
87
import { MobileNavbar } from "../mobile-navbar";
98
import { useTheme } from "next-themes";
@@ -15,8 +14,11 @@ import Toolbar from "../Toolbar";
1514
import ScreenLoading from "../ScreenLoading";
1615
import CollaborationStart from "../CollaborationStartBtn";
1716
import { cn } from "@/lib/utils";
17+
import { RoomParticipants, WebSocketMessage, WS_DATA_TYPE } from "@repo/common/types";
1818

19-
export function CanvasSheet({ roomName, roomId, userId, userName, token }: { roomName: string; roomId: string; userId: string; userName: string; token: string }) {
19+
const WS_URL = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:8080';
20+
21+
export const CanvasSheet = React.memo(function CanvasSheet({ roomName, roomId, userId, userName, token }: { roomName: string; roomId: string; userId: string; userName: string; token: string }) {
2022
const { theme } = useTheme()
2123
const canvasRef = useRef<HTMLCanvasElement>(null);
2224
const [game, setGame] = useState<Game>();
@@ -28,7 +30,6 @@ export function CanvasSheet({ roomName, roomId, userId, userName, token }: { roo
2830
const [strokeEdge, setStrokeEdge] = useState<StrokeEdge>("round");
2931
const [strokeStyle, setStrokeStyle] = useState<StrokeStyle>("solid");
3032
const [grabbing, setGrabbing] = useState(false);
31-
const [existingShapes, setExistingShapes] = useState<Shape[]>([]);
3233
const paramsRef = useRef({ roomId, roomName, userId, userName });
3334
const activeToolRef = useRef(activeTool);
3435
const strokeFillRef = useRef(strokeFill);
@@ -39,22 +40,73 @@ export function CanvasSheet({ roomName, roomId, userId, userName, token }: { roo
3940
const [sidebarOpen, setSidebarOpen] = useState(false);
4041
const [canvasColor, setCanvasColor] = useState<string>(canvasBgLight[0]);
4142
const canvasColorRef = useRef(canvasColor);
43+
const { matches, isLoading } = useMediaQuery('md');
44+
const [socket, setSocket] = useState<WebSocket | null>(null);
45+
const [participants, setParticipants] = useState<RoomParticipants[]>([]);
46+
const gameRef = useRef<Game | null>(null);
47+
const socketConnected = useRef(false);
4248

43-
const { isConnected, messages, participants, sendDrawingData } = useWebSocket(
44-
roomId,
45-
roomName,
46-
userId,
47-
userName,
48-
token
49-
);
49+
useEffect(() => {
50+
console.log('E1')
51+
if (!roomId || !roomName || !userId || !userName || !token) return;
52+
if (socket || socketConnected.current) return;
53+
const wsUrl = `${WS_URL}?token=${encodeURIComponent(token)}`;
54+
const ws = new WebSocket(wsUrl);
55+
socketConnected.current = true;
56+
57+
ws.onopen = () => {
58+
ws.send(JSON.stringify({
59+
type: WS_DATA_TYPE.JOIN,
60+
roomId,
61+
roomName,
62+
userId,
63+
userName
64+
}));
65+
setSocket(ws);
66+
};
5067

51-
const { matches, isLoading } = useMediaQuery('md');
68+
ws.onmessage = (event) => {
69+
const data: WebSocketMessage = JSON.parse(event.data);
70+
71+
if (data.participants) {
72+
setParticipants(data.participants);
73+
} else if (data.type === WS_DATA_TYPE.USER_JOINED) {
74+
setParticipants(prev => {
75+
const exists = prev.some(p => p.userId === data.userId);
76+
if (!exists) {
77+
return [...prev, { userId: data.userId!, userName: data.userName! }];
78+
}
79+
return prev;
80+
});
81+
} else if (data.type === WS_DATA_TYPE.USER_LEFT) {
82+
setParticipants(prev => prev.filter(user => user.userId !== data.userId));
83+
}
84+
85+
if ([WS_DATA_TYPE.DRAW, WS_DATA_TYPE.ERASER, WS_DATA_TYPE.UPDATE].includes(data.type)) {
86+
gameRef.current?.handleWebSocketMessage(data);
87+
}
88+
};
89+
90+
ws.onerror = (error) => console.error('WebSocket error:', error);
91+
92+
return () => {
93+
ws.close();
94+
socketConnected.current = false;
95+
};
96+
}, [roomId, roomName, userId, userName, token]);
5297

5398
useEffect(() => {
99+
console.log('E2')
100+
paramsRef.current = { roomId, roomName, userId, userName };
101+
}, [roomId, roomName, userId, userName]);
102+
103+
useEffect(() => {
104+
console.log('E3')
54105
setCanvasColor(canvasBgLight[0]);
55106
}, [theme])
56107

57108
useEffect(() => {
109+
console.log('E4')
58110
const handleResize = () => {
59111
if (canvasRef.current && game) {
60112
const canvas = canvasRef.current;
@@ -68,95 +120,69 @@ export function CanvasSheet({ roomName, roomId, userId, userName, token }: { roo
68120
return () => window.removeEventListener('resize', handleResize);
69121
}, [game]);
70122

71-
useEffect(() => {
72-
paramsRef.current = { roomId, roomName, userId, userName };
73-
}, [roomId, roomName, userId, userName]);
74-
75-
useEffect(() => {
76-
if (messages.length > 0) {
77-
try {
78-
messages.forEach((message) => {
79-
try {
80-
const data = JSON.parse(message.message);
81-
console.log('ws msg data = ', data)
82-
if (data.type === "draw") {
83-
const shape = JSON.parse(data.data).shape;
84-
setExistingShapes((prevShapes) => [...prevShapes, shape]);
85-
} else if (data.type === "eraser") {
86-
const shape = JSON.parse(data.data).shape;
87-
setExistingShapes((prevShapes) =>
88-
prevShapes.filter((s) => JSON.stringify(s) !== JSON.stringify(shape))
89-
);
90-
}
91-
} catch (e) {
92-
console.error("Error processing message:", e);
93-
}
94-
});
95-
} catch (e) {
96-
console.error("Error processing messages:", e);
97-
}
98-
}
99-
}, [messages]);
100-
101-
useEffect(() => {
102-
game?.setTool(activeTool)
103-
game?.setStrokeWidth(strokeWidth)
104-
game?.setStrokeFill(strokeFill)
105-
game?.setBgFill(bgFill)
106-
game?.setCanvasBgColor(canvasColor)
107-
game?.setStrokeEdge(strokeEdge)
108-
});
123+
// useEffect(() => {
124+
// console.log('E5')
125+
// game?.setTool(activeTool)
126+
// game?.setStrokeWidth(strokeWidth)
127+
// game?.setStrokeFill(strokeFill)
128+
// game?.setBgFill(bgFill)
129+
// game?.setCanvasBgColor(canvasColor)
130+
// game?.setStrokeEdge(strokeEdge)
131+
// });
109132

110133
useEffect(() => {
134+
console.log('E6')
111135
strokeEdgeRef.current = strokeEdge;
112136
game?.setStrokeEdge(strokeEdge);
113137
}, [strokeEdge, game]);
114138

115139
useEffect(() => {
140+
console.log('E7')
116141
strokeStyleRef.current = strokeStyle;
117142
game?.setStrokeStyle(strokeStyle);
118143
}, [strokeStyle, game]);
119144

120145
useEffect(() => {
146+
console.log('E8')
121147
activeToolRef.current = activeTool;
122148
game?.setTool(activeTool);
123149
}, [activeTool, game]);
124150

125151
useEffect(() => {
152+
console.log('E9')
126153
strokeWidthRef.current = strokeWidth;
127154
game?.setStrokeWidth(strokeWidth);
128155
}, [strokeWidth, game]);
129156

130157
useEffect(() => {
158+
console.log('E10')
131159
strokeFillRef.current = strokeFill;
132160
game?.setStrokeFill(strokeFill);
133161
}, [strokeFill, game]);
134162

135163
useEffect(() => {
164+
console.log('E11')
136165
bgFillRef.current = bgFill;
137166
game?.setBgFill(bgFill);
138167
}, [bgFill, game]);
139168

140169
useEffect(() => {
141-
if (game && existingShapes.length >= 0) {
142-
game.updateShapes(existingShapes);
143-
}
144-
}, [game, existingShapes]);
145-
146-
useEffect(() => {
170+
console.log('E12')
147171
if (game && canvasColorRef.current !== canvasColor) {
148172
canvasColorRef.current = canvasColor;
149173
game.setCanvasBgColor(canvasColor);
150174
}
151175
}, [canvasColor, game]);
152176

153177
useEffect(() => {
178+
console.log('E13')
154179
if (game) {
155180
game.setScale(scale);
156181
}
157182
}, [scale, game]);
158183

159184
useEffect(() => {
185+
console.log('E14')
160186
const handleKeyDown = (e: KeyboardEvent) => {
161187
switch (e.key) {
162188
case "1":
@@ -188,60 +214,46 @@ export function CanvasSheet({ roomName, roomId, userId, userName, token }: { roo
188214
return () => {
189215
document.removeEventListener("keydown", handleKeyDown);
190216
};
191-
}, [setActiveTool]);
192-
193-
const handleSendDrawing = useCallback((drawingData: string) => {
194-
if (isConnected) {
195-
sendDrawingData(JSON.stringify(drawingData));
196-
197-
}
198-
}, [isConnected, sendDrawingData]);
199-
200-
// const handleEraserComplete = useCallback((eraserData: string) => {
201-
// if (isConnected) {
202-
// sendEraserData(JSON.stringify(eraserData));
217+
}, []);
203218

219+
// useEffect(() => {
220+
// console.log('E15')
221+
// try {
222+
// console.log('participants = ', participants)
223+
// } catch (e) {
224+
// console.error("Error processing messages:", e);
204225
// }
205-
// }, [isConnected, sendEraserData]);
206-
207-
useEffect(() => {
208-
try {
209-
console.log('participants = ', participants)
210-
} catch (e) {
211-
console.error("Error processing messages:", e);
212-
}
213-
}, [participants]);
226+
// }, [participants]);
214227

215228
useEffect(() => {
216-
if (canvasRef.current) {
217-
const game = new Game(
218-
canvasRef.current,
219-
paramsRef.current.roomId,
220-
canvasColorRef.current,
221-
handleSendDrawing,
222-
paramsRef.current.roomName,
223-
(newScale) => setScale(newScale),
224-
[]
225-
)
226-
setGame(game);
227-
228-
game.setTool(activeToolRef.current);
229-
game.setStrokeWidth(strokeWidthRef.current);
230-
game.setStrokeFill(strokeFillRef.current);
231-
game.setBgFill(bgFillRef.current);
232-
game.setStrokeEdge(strokeEdgeRef.current);
233-
234-
canvasRef.current.width = window.innerWidth;
235-
canvasRef.current.height = window.innerHeight;
229+
console.log('E16')
230+
if (!canvasRef.current || !socket || gameRef.current) return;
231+
232+
const game = new Game(
233+
canvasRef.current,
234+
paramsRef.current.roomId,
235+
canvasColorRef.current,
236+
paramsRef.current.roomName,
237+
(newScale) => setScale(newScale),
238+
[],
239+
false,
240+
socket
241+
);
242+
243+
gameRef.current = game;
244+
setGame(game);
245+
246+
canvasRef.current.width = window.innerWidth;
247+
canvasRef.current.height = window.innerHeight;
236248

237-
return () => {
238-
game.destroy();
239-
}
240-
}
241-
242-
}, [canvasRef, handleSendDrawing]);
249+
return () => {
250+
gameRef.current?.destroy();
251+
gameRef.current = null;
252+
};
253+
}, [socket]);
243254

244255
useEffect(() => {
256+
console.log('E17')
245257
if (activeTool === "grab") {
246258
const handleGrab = () => {
247259
setGrabbing((prev) => !prev)
@@ -257,20 +269,24 @@ export function CanvasSheet({ roomName, roomId, userId, userName, token }: { roo
257269
}
258270
}, [activeTool]);
259271

260-
useEffect(() => {
261-
if (game?.outputScale) {
262-
setScale(game.outputScale);
263-
}
264-
}, [game?.outputScale]);
272+
// useEffect(() => {
273+
// console.log('E18')
274+
// if (game?.outputScale) {
275+
// setScale(game.outputScale);
276+
// }
277+
// }, [game?.outputScale]);
265278

266279
const toggleSidebar = useCallback(() => {
280+
console.log('E19')
267281
setSidebarOpen(prev => !prev);
268282
}, []);
269283

270284
if (isLoading) {
271-
return (
272-
<ScreenLoading />
273-
)
285+
return <ScreenLoading />
286+
}
287+
288+
if (!socket) {
289+
return <ScreenLoading content="Connecting ..." />
274290
}
275291

276292
return (
@@ -352,4 +368,4 @@ export function CanvasSheet({ roomName, roomId, userId, userName, token }: { roo
352368
<canvas className={cn("collabydraw collabydraw-canvas", theme === 'dark' ? 'collabydraw-canvas-dark' : '')} ref={canvasRef} />
353369
</div >
354370
)
355-
}
371+
});

0 commit comments

Comments
 (0)