Files
Momento/memento-note/hooks/use-brainstorm-socket.ts
Antigravity 37d9bea7bb
Some checks failed
CI / Lint, Test & Build (push) Successful in 12m43s
CI / Deploy production (on server) (push) Has been cancelled
fix: change socket port to 3005 (conflict with grafana) and make CI artifacts non-blocking
2026-05-19 21:06:34 +00:00

110 lines
3.2 KiB
TypeScript

'use client'
import { useEffect, useRef, useState, useCallback } from 'react'
import { io, Socket } from 'socket.io-client'
export interface PresenceUser {
userId: string
name: string
cursor: { x: number; y: number } | null
color: string
}
export interface ActivityEvent {
action: string
userId: string
userName: string
details: any
}
const SOCKET_URL = process.env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3005'
export function useBrainstormSocket(
sessionId: string | null,
userId: string | null,
userName: string | null,
onIdeaMoved?: (data: { ideaId: string; positionX: number; positionY: number }) => void
) {
const socketRef = useRef<Socket | null>(null)
const onIdeaMovedRef = useRef(onIdeaMoved)
onIdeaMovedRef.current = onIdeaMoved
const [others, setOthers] = useState<PresenceUser[]>([])
const [activities, setActivities] = useState<ActivityEvent[]>([])
const [aiProcessingNodeId, setAiProcessingNodeId] = useState<string | null>(null)
const guestIdRef = useRef<string | null>(null)
if (!guestIdRef.current && !userId) {
guestIdRef.current = `guest_${Math.random().toString(36).slice(2, 10)}`
}
const effectiveUserId = userId || guestIdRef.current!
useEffect(() => {
if (!sessionId) return
const socket = io(SOCKET_URL, {
auth: {
userId: effectiveUserId,
sessionId,
name: userName || 'Guest',
isGuest: !userId,
},
transports: ['websocket'],
autoConnect: true,
})
socketRef.current = socket
socket.on('presence:update', (users: PresenceUser[]) => {
setOthers(users.filter(u => u.userId !== effectiveUserId))
})
socket.on('cursor:update', (data: { userId: string; cursor: { x: number; y: number } }) => {
setOthers(prev => prev.map(u =>
u.userId === data.userId ? { ...u, cursor: data.cursor } : u
))
})
socket.on('activity:new', (event: ActivityEvent) => {
setActivities(prev => [event, ...prev].slice(0, 50))
})
socket.on('idea:added', () => {})
socket.on('idea:dismissed', () => {})
socket.on('idea:moved', (data: { ideaId: string; positionX: number; positionY: number }) => {
onIdeaMovedRef.current?.(data)
})
socket.on('idea:ai_processing', (data: { ideaId: string }) => {
setAiProcessingNodeId(data.ideaId)
})
socket.on('idea:ai_completed', (data: { ideaId: string }) => {
setAiProcessingNodeId(prev => prev === data.ideaId ? null : prev)
})
socket.on('idea:ai_failed', (data: { ideaId: string }) => {
setAiProcessingNodeId(prev => prev === data.ideaId ? null : prev)
})
return () => {
socket.disconnect()
socketRef.current = null
}
}, [sessionId, effectiveUserId, userName])
const moveCursor = useCallback((cursor: { x: number; y: number } | null) => {
socketRef.current?.emit('cursor:move', cursor)
}, [])
const broadcastActivity = useCallback((action: string, details?: any) => {
socketRef.current?.emit('activity:new', {
action,
userId: effectiveUserId,
userName: userName || 'Guest',
details,
})
}, [effectiveUserId, userName])
return { others, activities, moveCursor, broadcastActivity, socketRef, aiProcessingNodeId }
}