Skip to content
This repository was archived by the owner on Feb 9, 2026. It is now read-only.

Commit cc2b1d5

Browse files
authored
Merge branch 'main' into feature/db-update-urls
2 parents a92174a + f8baeb1 commit cc2b1d5

File tree

3 files changed

+168
-3
lines changed

3 files changed

+168
-3
lines changed

frontend/biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/2.3.5/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.3.6/schema.json",
33
"vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false },
44
"files": { "ignoreUnknown": false },
55
"formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2 },
Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,77 @@
1-
/* 接続、再接続、切断のライフサイクル管理の実装 */
1+
/* メッセージの受信と送信のイベント管理の実装 */
2+
import { useCallback, useEffect, useRef, useState } from "react";
3+
4+
/* 送受信するデータ型の設定 */
5+
export type MemberId = number;
6+
7+
export interface Member {
8+
id: MemberId;
9+
name: string;
10+
}
11+
12+
export interface Payment {
13+
id: number;
14+
amount: number | "";
15+
paidBy: MemberId;
16+
paidFor: MemberId;
17+
}
18+
19+
export type WsMessage = Member | Payment;
20+
21+
/* 接続を管理するカスタムフック */
22+
export const useChatHandler = (url: string) => {
23+
const wsRef = useRef<WebSocket | null>(null);
24+
const [isConnected, setIsConnected] = useState(false);
25+
const onMessageRef = useRef<((msg: WsMessage) => void) | null>(null);
26+
27+
useEffect(() => {
28+
/* WebSocket接続の確立 */
29+
const ws = new WebSocket(url);
30+
wsRef.current = ws;
31+
32+
/* WebSocketイベントのハンドラ */
33+
const handleOpen = () => {
34+
setIsConnected(true);
35+
};
36+
37+
const handleClose = () => {
38+
setIsConnected(false);
39+
};
40+
41+
const handleMessage = (event: MessageEvent) => {
42+
try {
43+
const data = JSON.parse(event.data) as WsMessage;
44+
onMessageRef.current?.(data);
45+
} catch (_error) {
46+
console.error("Failed to parse WebSocket message:", event.data);
47+
}
48+
};
49+
50+
ws.addEventListener("open", handleOpen);
51+
ws.addEventListener("close", handleClose);
52+
ws.addEventListener("message", handleMessage);
53+
54+
return () => {
55+
ws.close();
56+
ws.removeEventListener("open", handleOpen);
57+
ws.removeEventListener("close", handleClose);
58+
ws.removeEventListener("message", handleMessage);
59+
};
60+
}, [url]);
61+
62+
/* メッセージ受信時 */
63+
const setOnMessage = (handler: (msg: WsMessage) => void) => {
64+
onMessageRef.current = handler;
65+
};
66+
67+
/* メッセージ送信時 */
68+
const sendMessage = useCallback((msg: WsMessage) => {
69+
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
70+
console.error("WebSocket is not connected.");
71+
return;
72+
}
73+
wsRef.current.send(JSON.stringify(msg));
74+
}, []);
75+
76+
return { isConnected, sendMessage, setOnMessage };
77+
};

frontend/src/hooks/useWebSocket.ts

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,90 @@
1-
/* メッセージの受信と送信のイベント管理の実装 */
1+
/* 接続、再接続、切断のライフサイクル管理の実装 */
2+
3+
import { useCallback, useEffect, useRef, useState } from "react";
4+
5+
interface UseWebSocketOptions {
6+
reconnect?: boolean;
7+
reconnectInterval?: number;
8+
}
9+
10+
export function useWebSocket<T = unknown>(
11+
url: string,
12+
options: UseWebSocketOptions = {},
13+
) {
14+
const { reconnect = true, reconnectInterval = 3000 } = options;
15+
16+
const socketRef = useRef<WebSocket | null>(null);
17+
18+
const reconnectTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
19+
20+
const messageHandlerRef = useRef<((data: T) => void) | null>(null);
21+
22+
const [isConnected, setIsConnected] = useState(false);
23+
24+
const connect = useCallback(() => {
25+
if (socketRef.current) return;
26+
27+
const socket = new WebSocket(url);
28+
socketRef.current = socket;
29+
30+
socket.onopen = () => {
31+
console.log("[WebSocket] Connected:", url);
32+
setIsConnected(true);
33+
34+
if (reconnectTimerRef.current) {
35+
clearTimeout(reconnectTimerRef.current);
36+
reconnectTimerRef.current = null;
37+
}
38+
};
39+
40+
socket.onmessage = (event) => {
41+
try {
42+
const parsed = JSON.parse(event.data) as T;
43+
messageHandlerRef.current?.(parsed);
44+
} catch (_err) {
45+
console.error("[WebSocket] Failed to parse message:", event.data);
46+
}
47+
};
48+
49+
socket.onclose = () => {
50+
console.log("[WebSocket] Disconnected:", url);
51+
setIsConnected(false);
52+
socketRef.current = null;
53+
54+
if (reconnect) {
55+
reconnectTimerRef.current = setTimeout(connect, reconnectInterval);
56+
}
57+
};
58+
59+
socket.onerror = (err) => {
60+
console.error("[WebSocket] Error:", err);
61+
socket.close();
62+
};
63+
}, [url, reconnect, reconnectInterval]);
64+
65+
useEffect(() => {
66+
connect();
67+
return () => {
68+
if (reconnectTimerRef.current) clearTimeout(reconnectTimerRef.current);
69+
socketRef.current?.close();
70+
};
71+
}, [connect]);
72+
73+
const sendMessage = useCallback((message: T) => {
74+
if (socketRef.current?.readyState === WebSocket.OPEN) {
75+
socketRef.current.send(JSON.stringify(message));
76+
} else {
77+
console.warn("[WebSocket] Cannot send message. Not connected.");
78+
}
79+
}, []);
80+
81+
const setOnMessage = useCallback((handler: (data: T) => void) => {
82+
messageHandlerRef.current = handler;
83+
}, []);
84+
85+
return {
86+
isConnected,
87+
sendMessage,
88+
setOnMessage,
89+
};
90+
}

0 commit comments

Comments
 (0)