({
sessionId,
currentUser,
wsUrl,
}: UseCollaborationOptions)
| 32 | // ─── Hook ───────────────────────────────────────────────────────────────────── |
| 33 | |
| 34 | export function useCollaboration({ |
| 35 | sessionId, |
| 36 | currentUser, |
| 37 | wsUrl, |
| 38 | }: UseCollaborationOptions) { |
| 39 | const socketRef = useRef<CollabSocket | null>(null); |
| 40 | const [state, setState] = useState<CollaborationState>({ |
| 41 | isConnected: false, |
| 42 | myRole: null, |
| 43 | pendingToolUses: [], |
| 44 | annotations: {}, |
| 45 | toolApprovalPolicy: "any-collaborator", |
| 46 | }); |
| 47 | |
| 48 | const effectiveWsUrl = |
| 49 | wsUrl ?? |
| 50 | (typeof process !== "undefined" |
| 51 | ? process.env.NEXT_PUBLIC_WS_URL ?? "ws://localhost:3001" |
| 52 | : "ws://localhost:3001"); |
| 53 | |
| 54 | useEffect(() => { |
| 55 | const socket = new CollabSocket(sessionId, currentUser.id); |
| 56 | socketRef.current = socket; |
| 57 | |
| 58 | socket.onConnectionChange = (connected) => { |
| 59 | setState((s) => ({ ...s, isConnected: connected })); |
| 60 | }; |
| 61 | |
| 62 | const cleanup: Array<() => void> = []; |
| 63 | |
| 64 | cleanup.push( |
| 65 | socket.on("session_state", (e) => { |
| 66 | const me = e.users.find((u) => u.id === currentUser.id); |
| 67 | setState((s) => ({ |
| 68 | ...s, |
| 69 | myRole: me?.role ?? null, |
| 70 | toolApprovalPolicy: e.toolApprovalPolicy, |
| 71 | })); |
| 72 | }) |
| 73 | ); |
| 74 | |
| 75 | cleanup.push( |
| 76 | socket.on("tool_use_pending", (e: ToolUsePendingEvent) => { |
| 77 | const entry: PendingToolUse = { |
| 78 | id: e.toolUseId, |
| 79 | name: e.toolName, |
| 80 | input: e.toolInput, |
| 81 | messageId: e.messageId, |
| 82 | requestedAt: e.timestamp, |
| 83 | }; |
| 84 | setState((s) => ({ |
| 85 | ...s, |
| 86 | pendingToolUses: [...s.pendingToolUses, entry], |
| 87 | })); |
| 88 | }) |
| 89 | ); |
| 90 | |
| 91 | cleanup.push( |
no test coverage detected