({ socket, sessionId, currentUser }: UsePresenceOptions)
| 24 | // ─── Hook ───────────────────────────────────────────────────────────────────── |
| 25 | |
| 26 | export function usePresence({ socket, sessionId, currentUser }: UsePresenceOptions) { |
| 27 | const [presence, setPresence] = useState<PresenceState>(createPresenceState); |
| 28 | const typingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| 29 | const isTypingRef = useRef(false); |
| 30 | |
| 31 | useEffect(() => { |
| 32 | if (!socket) return; |
| 33 | const cleanup: Array<() => void> = []; |
| 34 | |
| 35 | cleanup.push( |
| 36 | socket.on("presence_sync", (e) => { |
| 37 | setPresence((s) => presenceSyncUsers(s, e.users)); |
| 38 | }) |
| 39 | ); |
| 40 | |
| 41 | cleanup.push( |
| 42 | socket.on("session_state", (e) => { |
| 43 | setPresence((s) => presenceSyncUsers(s, e.users)); |
| 44 | }) |
| 45 | ); |
| 46 | |
| 47 | cleanup.push( |
| 48 | socket.on("user_joined", (e) => { |
| 49 | setPresence((s) => presenceAddUser(s, e.user)); |
| 50 | }) |
| 51 | ); |
| 52 | |
| 53 | cleanup.push( |
| 54 | socket.on("user_left", (e) => { |
| 55 | setPresence((s) => presenceRemoveUser(s, e.user.id)); |
| 56 | }) |
| 57 | ); |
| 58 | |
| 59 | cleanup.push( |
| 60 | socket.on("cursor_update", (e) => { |
| 61 | if (e.userId === currentUser.id) return; // skip own cursor |
| 62 | setPresence((s) => |
| 63 | presenceUpdateCursor(s, e.userId, e.position, e.selectionStart, e.selectionEnd) |
| 64 | ); |
| 65 | }) |
| 66 | ); |
| 67 | |
| 68 | cleanup.push( |
| 69 | socket.on("typing_start", (e) => { |
| 70 | if (e.userId === currentUser.id) return; |
| 71 | setPresence((s) => presenceSetTyping(s, e.user.id, true)); |
| 72 | }) |
| 73 | ); |
| 74 | |
| 75 | cleanup.push( |
| 76 | socket.on("typing_stop", (e) => { |
| 77 | if (e.userId === currentUser.id) return; |
| 78 | setPresence((s) => presenceSetTyping(s, e.user.id, false)); |
| 79 | }) |
| 80 | ); |
| 81 | |
| 82 | return () => cleanup.forEach((off) => off()); |
| 83 | }, [socket, currentUser.id]); |
no test coverage detected