import { ClassAttendanceDto, ClassChatDto, HandRaiseDto, SignalingMessageDto, } 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 onSignalingMessage?: (message: SignalingMessageDto) => void private onAttendanceUpdate?: (record: ClassAttendanceDto) => void private onParticipantJoined?: (userId: string, name: string) => void private onParticipantLeft?: (userId: string) => void private onChatMessage?: (message: ClassChatDto) => void private onParticipantMuted?: (userId: string, isMuted: boolean) => void private onHandRaiseReceived?: (handRaise: HandRaiseDto) => void private onHandRaiseDismissed?: (handRaiseId: 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('ReceiveSignalingMessage', (message: SignalingMessageDto) => { this.onSignalingMessage?.(message) }) this.connection.on('AttendanceUpdated', (record: ClassAttendanceDto) => { 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', (handRaise: HandRaiseDto) => { this.onHandRaiseReceived?.(handRaise) }) this.connection.on('HandRaiseDismissed', (handRaiseId: string) => { this.onHandRaiseDismissed?.(handRaiseId) }) 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, userName: string): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection join class for', userName) // Simulate successful join in demo mode // Don't auto-add participants in demo mode - let manual simulation handle this return } try { await this.connection.invoke('JoinClass', sessionId, userName) } 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 sendSignalingMessage(message: SignalingMessageDto): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection signaling message', message.type) // In demo mode, we can't send real signaling messages // WebRTC will need to work in local-only mode return } try { await this.connection.invoke('SendSignalingMessage', message) } catch (error) { console.error('Error sending signaling message:', 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: ClassChatDto = { id: `msg-${Date.now()}`, 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: ClassChatDto = { id: `msg-${Date.now()}`, 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, ) } catch (error) { console.error('Error sending private message:', error) } } async sendAnnouncement( sessionId: string, senderId: string, senderName: string, message: string, ): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection simulating announcement from', senderName) const chatMessage: ClassChatDto = { id: `msg-${Date.now()}`, senderId, senderName, message, timestamp: new Date().toISOString(), isTeacher: true, messageType: 'announcement', } setTimeout(() => { this.onChatMessage?.(chatMessage) }, 100) return } try { await this.connection.invoke('SendAnnouncement', sessionId, senderId, senderName, message) } catch (error) { console.error('Error sending chat message:', error) } } async muteParticipant(sessionId: string, userId: string, isMuted: boolean): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection simulating mute participant', userId, isMuted) setTimeout(() => { this.onParticipantMuted?.(userId, isMuted) }, 100) return } try { await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted) } 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: `hand-${Date.now()}`, studentId, studentName, timestamp: new Date().toISOString(), isActive: true, } setTimeout(() => { this.onHandRaiseReceived?.(handRaise) }, 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, handRaiseId: string): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection simulating hand raise approval') setTimeout(() => { this.onHandRaiseDismissed?.(handRaiseId) }, 100) return } try { await this.connection.invoke('ApproveHandRaise', sessionId, handRaiseId) } catch (error) { console.error('Error approving hand raise:', error) } } async dismissHandRaise(sessionId: string, handRaiseId: string): Promise { if (!this.isConnected) { console.log('Error starting SignalR connection simulating hand raise dismissal') setTimeout(() => { this.onHandRaiseDismissed?.(handRaiseId) }, 100) return } try { await this.connection.invoke('DismissHandRaise', sessionId, handRaiseId) } catch (error) { console.error('Error dismissing hand raise:', error) } } setSignalingHandler(callback: (message: SignalingMessageDto) => void) { this.onSignalingMessage = callback } setAttendanceUpdatedHandler(callback: (record: ClassAttendanceDto) => 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: ClassChatDto) => void) { this.onChatMessage = callback } setParticipantMutedHandler(callback: (userId: string, isMuted: boolean) => void) { this.onParticipantMuted = callback } setHandRaiseReceivedHandler(callback: (handRaise: HandRaiseDto) => void) { this.onHandRaiseReceived = callback } setHandRaiseDismissedHandler(callback: (handRaiseId: 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 } }