치지직 채팅창 인식 추가 및 주사위 횟수 증가 추가.
This commit is contained in:
140
lib/rooms/client.ts
Normal file
140
lib/rooms/client.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
export type PublicRoomState = {
|
||||
id: string;
|
||||
settings: {
|
||||
chzzkEnabled: boolean;
|
||||
liveUrl: string;
|
||||
channelId: string | null;
|
||||
cheesePerDice: number;
|
||||
multiplyByCheese: boolean;
|
||||
maxMultiplier: number;
|
||||
};
|
||||
diceQueue: number;
|
||||
channelName: string | null;
|
||||
chatChannelId: string | null;
|
||||
chatStatus: 'idle' | 'connecting' | 'connected' | 'error' | 'disconnected';
|
||||
chatError: string | null;
|
||||
lastDonations: Array<{
|
||||
id: string;
|
||||
at: number;
|
||||
payAmount: number;
|
||||
diceAdded: number;
|
||||
donatorNickname: string;
|
||||
donationText?: string;
|
||||
simulated?: boolean;
|
||||
}>;
|
||||
chatFeed: Array<{
|
||||
id: string;
|
||||
at: number;
|
||||
nickname: string;
|
||||
message: string;
|
||||
kind: 'chat' | 'donation';
|
||||
payAmount?: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
export function useRoom(roomId: string | null) {
|
||||
const [room, setRoom] = useState<PublicRoomState | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!roomId) return;
|
||||
|
||||
const clientId =
|
||||
typeof crypto !== 'undefined' && 'randomUUID' in crypto
|
||||
? crypto.randomUUID()
|
||||
: `c-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
||||
|
||||
void fetch(`/api/rooms/${roomId}`)
|
||||
.then((r) => r.json())
|
||||
.then((j) => {
|
||||
if (j.room) setRoom(j.room as PublicRoomState);
|
||||
else setError(j.error || '방을 불러오지 못했습니다.');
|
||||
})
|
||||
.catch(() => setError('방을 불러오지 못했습니다.'));
|
||||
|
||||
const es = new EventSource(
|
||||
`/api/rooms/${roomId}/events?clientId=${encodeURIComponent(clientId)}`,
|
||||
);
|
||||
es.addEventListener('room', (ev) => {
|
||||
try {
|
||||
setRoom(JSON.parse((ev as MessageEvent).data) as PublicRoomState);
|
||||
setError(null);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
es.onerror = () => {
|
||||
// keep last state; browser will retry SSE
|
||||
};
|
||||
|
||||
const leave = () => {
|
||||
try {
|
||||
const payload = JSON.stringify({ clientId });
|
||||
if (typeof navigator !== 'undefined' && navigator.sendBeacon) {
|
||||
navigator.sendBeacon(
|
||||
`/api/rooms/${roomId}/leave`,
|
||||
new Blob([payload], { type: 'application/json' }),
|
||||
);
|
||||
} else {
|
||||
void fetch(`/api/rooms/${roomId}/leave`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: payload,
|
||||
keepalive: true,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('pagehide', leave);
|
||||
return () => {
|
||||
window.removeEventListener('pagehide', leave);
|
||||
es.close();
|
||||
leave();
|
||||
};
|
||||
}, [roomId]);
|
||||
|
||||
const consumeDice = useCallback(
|
||||
async (count = 1) => {
|
||||
if (!roomId) return { ok: true as const };
|
||||
const res = await fetch(`/api/rooms/${roomId}/consume-dice`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ count }),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!res.ok) {
|
||||
return {
|
||||
ok: false as const,
|
||||
error: (json.error as string) || '주사위를 소모할 수 없습니다.',
|
||||
};
|
||||
}
|
||||
setRoom(json.room as PublicRoomState);
|
||||
return { ok: true as const };
|
||||
},
|
||||
[roomId],
|
||||
);
|
||||
|
||||
const simulateDonation = useCallback(
|
||||
async (payAmount: number) => {
|
||||
if (!roomId) return;
|
||||
const res = await fetch(`/api/rooms/${roomId}/simulate-donation`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ payAmount }),
|
||||
});
|
||||
const json = await res.json();
|
||||
if (!res.ok) throw new Error(json.error || '시뮬레이션 실패');
|
||||
setRoom(json.room as PublicRoomState);
|
||||
},
|
||||
[roomId],
|
||||
);
|
||||
|
||||
return { room, error, consumeDice, simulateDonation };
|
||||
}
|
||||
Reference in New Issue
Block a user