erp-platform/ui/src/services/classroom/signalr.ts

395 lines
11 KiB
TypeScript
Raw Normal View History

2025-08-26 08:39:09 +00:00
import {
ClassAttendanceDto,
ClassChatDto,
HandRaiseDto,
SignalingMessageDto,
} from '@/proxy/classroom/models'
2025-08-27 20:55:01 +00:00
import { store } from '@/store/store'
2025-08-26 08:39:09 +00:00
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() {
2025-08-27 20:55:01 +00:00
const { auth } = store.getState()
2025-08-26 08:39:09 +00:00
// Only initialize connection if not in demo mode
2025-08-28 11:53:47 +00:00
// 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()
2025-08-26 08:39:09 +00:00
}
private setupEventHandlers() {
2025-08-28 11:53:47 +00:00
if (!this.connection) return
2025-08-26 08:39:09 +00:00
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')
})
2025-08-27 20:55:01 +00:00
this.connection.on('Error', (message: string) => {
console.error('Hub error:', message)
})
2025-08-26 08:39:09 +00:00
}
async start(): Promise<void> {
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
}
}
2025-08-27 20:55:01 +00:00
async joinClass(sessionId: string, userName: string): Promise<void> {
2025-08-28 11:53:47 +00:00
if (!this.isConnected) {
console.log('Error starting SignalR connection join class for', userName)
2025-08-26 08:39:09 +00:00
// Simulate successful join in demo mode
// Don't auto-add participants in demo mode - let manual simulation handle this
return
}
try {
2025-08-27 20:55:01 +00:00
await this.connection.invoke('JoinClass', sessionId, userName)
2025-08-26 08:39:09 +00:00
} catch (error) {
console.error('Error joining class:', error)
}
}
2025-08-27 20:55:01 +00:00
async leaveClass(sessionId: string): Promise<void> {
const { auth } = store.getState()
2025-08-28 11:53:47 +00:00
if (!this.isConnected) {
console.log('Error starting SignalR connection simulating leave class for user', auth.user.id)
2025-08-26 08:39:09 +00:00
// Simulate successful leave in demo mode
setTimeout(() => {
2025-08-27 20:55:01 +00:00
this.onParticipantLeft?.(auth.user.id)
2025-08-26 08:39:09 +00:00
}, 100)
return
}
try {
2025-08-27 20:55:01 +00:00
await this.connection.invoke('LeaveClass', sessionId)
2025-08-26 08:39:09 +00:00
} catch (error) {
console.error('Error leaving class:', error)
}
}
async sendSignalingMessage(message: SignalingMessageDto): Promise<void> {
2025-08-28 11:53:47 +00:00
if (!this.isConnected) {
console.log('Error starting SignalR connection signaling message', message.type)
2025-08-26 08:39:09 +00:00
// 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<void> {
2025-08-28 11:53:47 +00:00
if (!this.isConnected) {
console.log('Error starting SignalR connection simulating chat message from', senderName)
2025-08-26 08:39:09 +00:00
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<void> {
2025-08-28 11:53:47 +00:00
if (!this.isConnected) {
console.log(
'Error starting SignalR connection simulating private message from',
senderName,
'to',
recipientName,
)
2025-08-26 08:39:09 +00:00
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<void> {
2025-08-28 11:53:47 +00:00
if (!this.isConnected) {
console.log('Error starting SignalR connection simulating announcement from', senderName)
2025-08-26 08:39:09 +00:00
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<void> {
2025-08-28 11:53:47 +00:00
if (!this.isConnected) {
console.log('Error starting SignalR connection simulating mute participant', userId, isMuted)
2025-08-26 08:39:09 +00:00
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<void> {
2025-08-28 11:53:47 +00:00
if (!this.isConnected) {
console.log('Error starting SignalR connection simulating hand raise from', studentName)
2025-08-26 08:39:09 +00:00
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<void> {
2025-08-28 11:53:47 +00:00
if (!this.isConnected) {
console.log('Error starting SignalR connection simulating kick participant', participantId)
2025-08-26 08:39:09 +00:00
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<void> {
2025-08-28 11:53:47 +00:00
if (!this.isConnected) {
console.log('Error starting SignalR connection simulating hand raise approval')
2025-08-26 08:39:09 +00:00
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<void> {
2025-08-28 11:53:47 +00:00
if (!this.isConnected) {
console.log('Error starting SignalR connection simulating hand raise dismissal')
2025-08-26 08:39:09 +00:00
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<void> {
if (this.isConnected && this.connection) {
await this.connection.stop()
this.isConnected = false
}
}
getConnectionState(): boolean {
return this.isConnected
}
}