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

391 lines
11 KiB
TypeScript

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<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
}
}
async joinClass(
sessionId: string,
userId: string,
userName: string,
isTeacher: boolean,
): Promise<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
if (this.isConnected && this.connection) {
await this.connection.stop()
this.isConnected = false
}
}
getConnectionState(): boolean {
return this.isConnected
}
}