96 lines
2.6 KiB
TypeScript
96 lines
2.6 KiB
TypeScript
import { useEffect, useRef, useState } from 'react';
|
|
import { io, Socket } from 'socket.io-client';
|
|
|
|
interface CursorPosition {
|
|
x: number;
|
|
y: number;
|
|
}
|
|
|
|
interface PeerMouseHook {
|
|
cursors: Record<string, CursorPosition>;
|
|
}
|
|
|
|
export function usePeerMouse(roomId: string): PeerMouseHook {
|
|
const [cursors, setCursors] = useState<Record<string, CursorPosition>>({});
|
|
const socketRef = useRef<Socket | null>(null);
|
|
const peers = useRef<Record<string, RTCPeerConnection>>({});
|
|
const channels = useRef<Record<string, RTCDataChannel>>({});
|
|
|
|
useEffect(() => {
|
|
const socket = io();
|
|
socketRef.current = socket;
|
|
|
|
socket.emit('join-room', roomId);
|
|
|
|
socket.on('new-peer', async (peerId: string) => {
|
|
const pc = createPeer(peerId, true);
|
|
const offer = await pc.createOffer();
|
|
await pc.setLocalDescription(offer);
|
|
socket.emit('signal', { to: peerId, data: { description: pc.localDescription } });
|
|
});
|
|
|
|
socket.on('signal', async ({ from, data }) => {
|
|
const pc = peers.current[from] || createPeer(from, false);
|
|
|
|
if (data.description) {
|
|
await pc.setRemoteDescription(data.description);
|
|
|
|
if (data.description.type === 'offer') {
|
|
const answer = await pc.createAnswer();
|
|
await pc.setLocalDescription(answer);
|
|
socket.emit('signal', { to: from, data: { description: pc.localDescription } });
|
|
}
|
|
}
|
|
|
|
if (data.candidate) {
|
|
await pc.addIceCandidate(data.candidate);
|
|
}
|
|
});
|
|
|
|
function createPeer(peerId: string, isInitiator: boolean): RTCPeerConnection {
|
|
const pc = new RTCPeerConnection();
|
|
|
|
if (isInitiator) {
|
|
const channel = pc.createDataChannel('mouse');
|
|
setupChannel(peerId, channel);
|
|
} else {
|
|
pc.ondatachannel = (e) => setupChannel(peerId, e.channel);
|
|
}
|
|
|
|
pc.onicecandidate = (e) => {
|
|
if (e.candidate) {
|
|
socket.emit('signal', { to: peerId, data: { candidate: e.candidate } });
|
|
}
|
|
};
|
|
|
|
peers.current[peerId] = pc;
|
|
return pc;
|
|
}
|
|
|
|
function setupChannel(peerId: string, channel: RTCDataChannel) {
|
|
channels.current[peerId] = channel;
|
|
channel.onmessage = (e) => {
|
|
const pos = JSON.parse(e.data);
|
|
setCursors((prev) => ({ ...prev, [peerId]: pos }));
|
|
};
|
|
}
|
|
|
|
function handleMouseMove(e: MouseEvent) {
|
|
const pos = JSON.stringify({ x: e.clientX, y: e.clientY });
|
|
Object.values(channels.current).forEach((ch) => {
|
|
if (ch.readyState === 'open') ch.send(pos);
|
|
});
|
|
}
|
|
|
|
window.addEventListener('mousemove', handleMouseMove);
|
|
|
|
return () => {
|
|
window.removeEventListener('mousemove', handleMouseMove);
|
|
socket.disconnect();
|
|
Object.values(peers.current).forEach((pc) => pc.close());
|
|
};
|
|
}, [roomId]);
|
|
|
|
return { cursors };
|
|
}
|