import { ClassroomAttendanceDto, ClassroomChatDto, HandRaiseDto } from '@/proxy/classroom/models' import { store } from '@/store/store' import * as signalR from '@microsoft/signalr' export class SignalRService { private connection!: signalR.HubConnection private isConnected: boolean = false private onAttendanceUpdate?: (record: ClassroomAttendanceDto) => void private onParticipantJoined?: (userId: string, name: string) => void private onParticipantLeft?: (userId: string) => void private onChatMessage?: (message: ClassroomChatDto) => void private onParticipantMuted?: (userId: string, isMuted: boolean) => void private onHandRaiseReceived?: (studentId: string) => void private onHandRaiseDismissed?: (studentId: string) => void constructor() { const { auth } = store.getState() // Only initialize connection if not in demo mode // In production, replace with your actual SignalR hub URL this.connection = new signalR.HubConnectionBuilder() .withUrl(`${import.meta.env.VITE_API_URL}/classroomhub`, { accessTokenFactory: () => auth.session.token || '', }) .withAutomaticReconnect() .configureLogging(signalR.LogLevel.Information) .build() this.setupEventHandlers() } private setupEventHandlers() { if (!this.connection) return this.connection.on('AttendanceUpdated', (record: ClassroomAttendanceDto) => { this.onAttendanceUpdate?.(record) }) this.connection.on('ParticipantJoined', (userId: string, name: string) => { this.onParticipantJoined?.(userId, name) }) this.connection.on('ParticipantLeft', (userId: string) => { this.onParticipantLeft?.(userId) }) this.connection.on('ChatMessage', (message: any) => { this.onChatMessage?.(message) }) this.connection.on('ParticipantMuted', (userId: string, isMuted: boolean) => { this.onParticipantMuted?.(userId, isMuted) }) this.connection.on('HandRaiseReceived', (payload: any) => { // payload = { handRaiseId, studentId, studentName, ... } this.onHandRaiseReceived?.(payload.studentId) }) this.connection.on('HandRaiseDismissed', (payload: any) => { // payload = { handRaiseId, studentId } this.onHandRaiseDismissed?.(payload.studentId) }) this.connection.onreconnected(() => { console.log('SignalR reconnected') }) this.connection.onclose(() => { console.log('SignalR connection closed') }) this.connection.on('Error', (message: string) => { console.error('Hub error:', message) }) } async start(): Promise { try { await this.connection.start() this.isConnected = true console.log('SignalR connection started') } catch (error) { console.error('Error starting SignalR connection:', error) // Switch to demo mode if connection fails this.isConnected = false } } async joinClass( sessionId: string, userId: string, userName: string, isTeacher: boolean, ): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection join class for', userName) return } console.log('Joining class session:', sessionId, 'as', userName, 'isTeacher:', isTeacher) try { await this.connection.invoke('JoinClass', sessionId, userId, userName, isTeacher) } catch (error) { console.error('Error joining class:', error) } } async leaveClass(sessionId: string): Promise { const { auth } = store.getState() if (!this.isConnected) { console.log('Error starting SignalR connection simulating leave class for user', auth.user.id) // Simulate successful leave in demo mode setTimeout(() => { this.onParticipantLeft?.(auth.user.id) }, 100) return } try { await this.connection.invoke('LeaveClass', sessionId) } catch (error) { console.error('Error leaving class:', error) } } async sendChatMessage( sessionId: string, senderId: string, senderName: string, message: string, isTeacher: boolean, ): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection simulating chat message from', senderName) const chatMessage: ClassroomChatDto = { id: crypto.randomUUID(), sessionId, senderId, senderName, message, timestamp: new Date().toISOString(), isTeacher, messageType: 'public', } setTimeout(() => { this.onChatMessage?.(chatMessage) }, 100) return } try { await this.connection.invoke( 'SendChatMessage', sessionId, senderId, senderName, message, isTeacher, 'public', ) } catch (error) { console.error('Error sending chat message:', error) } } async sendPrivateMessage( sessionId: string, senderId: string, senderName: string, message: string, recipientId: string, recipientName: string, isTeacher: boolean, ): Promise { if (!this.isConnected) { console.log( 'Error starting SignalR connection simulating private message from', senderName, 'to', recipientName, ) const chatMessage: ClassroomChatDto = { id: crypto.randomUUID(), sessionId, senderId, senderName, message, timestamp: new Date().toISOString(), isTeacher, recipientId, recipientName, messageType: 'private', } setTimeout(() => { this.onChatMessage?.(chatMessage) }, 100) return } try { await this.connection.invoke( 'SendPrivateMessage', sessionId, senderId, senderName, message, recipientId, recipientName, isTeacher, 'private', ) } catch (error) { console.error('Error sending private message:', error) } } async sendAnnouncement( sessionId: string, senderId: string, senderName: string, message: string, isTeacher: boolean, ): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection simulating announcement from', senderName) const chatMessage: ClassroomChatDto = { id: crypto.randomUUID(), sessionId, senderId, senderName, message, timestamp: new Date().toISOString(), isTeacher, messageType: 'announcement', } setTimeout(() => { this.onChatMessage?.(chatMessage) }, 100) return } try { await this.connection.invoke( 'SendAnnouncement', sessionId, senderId, senderName, message, isTeacher, ) } catch (error) { console.error('Error sending chat message:', error) } } async muteParticipant( sessionId: string, userId: string, isMuted: boolean, isTeacher: boolean, ): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection simulating mute participant', userId, isMuted) setTimeout(() => { this.onParticipantMuted?.(userId, isMuted) }, 100) return } console.log('Muting participant:', userId, 'Muted:', isMuted, 'isTeacher:', isTeacher) try { await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted, isTeacher) } catch (error) { console.error('Error muting participant:', error) } } async raiseHand(sessionId: string, studentId: string, studentName: string): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection simulating hand raise from', studentName) const handRaise: HandRaiseDto = { id: crypto.randomUUID(), studentId, studentName, timestamp: new Date().toISOString(), isActive: true, } setTimeout(() => { this.onHandRaiseReceived?.(studentId) }, 100) return } try { await this.connection.invoke('RaiseHand', sessionId, studentId, studentName) } catch (error) { console.error('Error raising hand:', error) } } async kickParticipant(sessionId: string, participantId: string): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection simulating kick participant', participantId) setTimeout(() => { this.onParticipantLeft?.(participantId) }, 100) return } try { await this.connection.invoke('KickParticipant', sessionId, participantId) } catch (error) { console.error('Error kicking participant:', error) } } async approveHandRaise(sessionId: string, studentId: string): Promise { if (!this.isConnected) { console.log('Simulating hand raise approval for student', studentId) setTimeout(() => { this.onHandRaiseDismissed?.(studentId) }, 100) return } try { await this.connection.invoke('ApproveHandRaise', sessionId, studentId) } catch (error) { console.error('Error approving hand raise:', error) } } async dismissHandRaise(sessionId: string, studentId: string): Promise { if (!this.isConnected) { console.log('Simulating hand raise dismissal for student', studentId) setTimeout(() => { this.onHandRaiseDismissed?.(studentId) }, 100) return } try { await this.connection.invoke('DismissHandRaise', sessionId, studentId) } catch (error) { console.error('Error dismissing hand raise:', error) } } setAttendanceUpdatedHandler(callback: (record: ClassroomAttendanceDto) => void) { this.onAttendanceUpdate = callback } setParticipantJoinHandler(callback: (userId: string, name: string) => void) { this.onParticipantJoined = callback } setParticipantLeaveHandler(callback: (userId: string) => void) { this.onParticipantLeft = callback } setChatMessageReceivedHandler(callback: (message: ClassroomChatDto) => void) { this.onChatMessage = callback } setParticipantMutedHandler(callback: (userId: string, isMuted: boolean) => void) { this.onParticipantMuted = callback } setHandRaiseReceivedHandler(callback: (studentId: string) => void) { this.onHandRaiseReceived = callback } setHandRaiseDismissedHandler(callback: (studentId: string) => void) { this.onHandRaiseDismissed = callback } async disconnect(): Promise { if (this.isConnected && this.connection) { await this.connection.stop() this.isConnected = false } } getConnectionState(): boolean { return this.isConnected } }