66 lines
1.8 KiB
TypeScript
66 lines
1.8 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
import { io, Socket } from 'socket.io-client';
|
|
|
|
interface UseSocketOptions<Sister = unknown, Activity = unknown> {
|
|
onSistersUpdate?: (sisters: Sister[]) => void;
|
|
onActivityNew?: (item: Activity) => void;
|
|
}
|
|
|
|
interface SistersUpdatePayload<Sister = unknown> {
|
|
sisters: Sister[];
|
|
}
|
|
|
|
interface ActivityNewPayload<Activity = unknown> {
|
|
item: Activity;
|
|
}
|
|
|
|
export function useSocket<Sister = unknown, Activity = unknown>(options: UseSocketOptions<Sister, Activity> = {}) {
|
|
const socketRef = useRef<Socket | null>(null);
|
|
const [connected, setConnected] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// next.config.ts rewrites가 없는 경우를 위해 빈 origin 처리
|
|
// 브라우저에서 WebSocket은 rewrites 대상이 아니므로 직접 BE URL 필요
|
|
const wsUrl = process.env.NEXT_PUBLIC_WS_URL ?? window.location.origin;
|
|
const token = localStorage.getItem('hanarang_access_token') ?? '';
|
|
|
|
const socket = io(`${wsUrl}/ws`, {
|
|
path: '/socket.io',
|
|
transports: ['websocket', 'polling'],
|
|
reconnectionAttempts: 5,
|
|
reconnectionDelay: 3000,
|
|
auth: { token },
|
|
});
|
|
|
|
socket.on('connect', () => {
|
|
setConnected(true);
|
|
});
|
|
|
|
socket.on('disconnect', () => {
|
|
setConnected(false);
|
|
});
|
|
|
|
socket.on('sisters:update', (data: SistersUpdatePayload<Sister>) => {
|
|
options.onSistersUpdate?.(data.sisters);
|
|
});
|
|
|
|
socket.on('activity:new', (data: ActivityNewPayload<Activity>) => {
|
|
options.onActivityNew?.(data.item);
|
|
});
|
|
|
|
socketRef.current = socket;
|
|
|
|
return () => {
|
|
socket.disconnect();
|
|
};
|
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
const ping = useCallback(() => {
|
|
socketRef.current?.emit('ping');
|
|
}, []);
|
|
|
|
return { connected, ping };
|
|
}
|