574 lines
16 KiB
TypeScript
574 lines
16 KiB
TypeScript
|
|
import { ROUTES_ENUM } from '@/routes/route.constant'
|
|||
|
|
import { store } from '@/store/store'
|
|||
|
|
import * as signalR from '@microsoft/signalr'
|
|||
|
|
import { toast } from '@/components/ui'
|
|||
|
|
import Notification from '@/components/ui/Notification'
|
|||
|
|
import { VideoroomAttendanceDto, VideoroomChatDto } from '@/proxy/videoroom/models'
|
|||
|
|
|
|||
|
|
export class SignalRService {
|
|||
|
|
private connection!: signalR.HubConnection
|
|||
|
|
private isConnected: boolean = false
|
|||
|
|
private currentSessionId?: string
|
|||
|
|
private isKicked: boolean = false
|
|||
|
|
|
|||
|
|
private onAttendanceUpdate?: (record: VideoroomAttendanceDto) => void
|
|||
|
|
private onParticipantJoined?: (
|
|||
|
|
userId: string,
|
|||
|
|
name: string,
|
|||
|
|
isTeacher: boolean,
|
|||
|
|
isActive: boolean,
|
|||
|
|
) => void
|
|||
|
|
private onParticipantLeft?: (payload: {
|
|||
|
|
userId: string
|
|||
|
|
sessionId: string
|
|||
|
|
userName: string
|
|||
|
|
}) => void
|
|||
|
|
private onChatMessage?: (message: VideoroomChatDto) => void
|
|||
|
|
private onParticipantMuted?: (userId: string, isMuted: boolean) => void
|
|||
|
|
private onHandRaiseReceived?: (studentId: string) => void
|
|||
|
|
private onHandRaiseDismissed?: (studentId: string) => void
|
|||
|
|
private onOfferReceived?: (fromUserId: string, offer: RTCSessionDescriptionInit) => void
|
|||
|
|
private onAnswerReceived?: (fromUserId: string, answer: RTCSessionDescriptionInit) => void
|
|||
|
|
private onIceCandidateReceived?: (fromUserId: string, candidate: RTCIceCandidateInit) => void
|
|||
|
|
private onForceCleanup?: () => void
|
|||
|
|
|
|||
|
|
constructor() {
|
|||
|
|
const { auth } = store.getState()
|
|||
|
|
|
|||
|
|
this.connection = new signalR.HubConnectionBuilder()
|
|||
|
|
.withUrl(`${import.meta.env.VITE_API_URL}/videoroomhub`, {
|
|||
|
|
accessTokenFactory: () => auth.session.token || '',
|
|||
|
|
})
|
|||
|
|
.configureLogging(signalR.LogLevel.Information)
|
|||
|
|
.build()
|
|||
|
|
|
|||
|
|
this.setupEventHandlers()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private setupEventHandlers() {
|
|||
|
|
if (!this.connection) return
|
|||
|
|
|
|||
|
|
this.connection.on('AttendanceUpdated', (record: VideoroomAttendanceDto) => {
|
|||
|
|
this.onAttendanceUpdate?.(record)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.connection.on(
|
|||
|
|
'ParticipantJoined',
|
|||
|
|
(userId: string, name: string, isTeacher: boolean, isActive: boolean) => {
|
|||
|
|
this.onParticipantJoined?.(userId, name, isTeacher, isActive)
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
this.connection.on(
|
|||
|
|
'ParticipantLeft',
|
|||
|
|
(payload: { userId: string; sessionId: string; userName: string }) => {
|
|||
|
|
this.onParticipantLeft?.(payload)
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
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) => {
|
|||
|
|
this.onHandRaiseReceived?.(payload.studentId)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.connection.on('HandRaiseDismissed', (payload: any) => {
|
|||
|
|
this.onHandRaiseDismissed?.(payload.studentId)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.connection.on('ReceiveOffer', (fromUserId: string, offer: RTCSessionDescriptionInit) => {
|
|||
|
|
this.onOfferReceived?.(fromUserId, offer)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.connection.on('ReceiveAnswer', (fromUserId: string, answer: RTCSessionDescriptionInit) => {
|
|||
|
|
this.onAnswerReceived?.(fromUserId, answer)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.connection.on(
|
|||
|
|
'ReceiveIceCandidate',
|
|||
|
|
(fromUserId: string, candidate: RTCIceCandidateInit) => {
|
|||
|
|
this.onIceCandidateReceived?.(fromUserId, candidate)
|
|||
|
|
},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
this.connection.onreconnected(async () => {
|
|||
|
|
this.isConnected = true
|
|||
|
|
toast.push(<Notification title="🔄 Bağlantı tekrar kuruldu" type="success" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (this.currentSessionId && store.getState().auth.user) {
|
|||
|
|
const u = store.getState().auth.user
|
|||
|
|
await this.joinClass(this.currentSessionId, u.id, u.name, u.role === 'teacher', true)
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.connection.onclose(async () => {
|
|||
|
|
if (this.isKicked) {
|
|||
|
|
toast.push(
|
|||
|
|
<Notification title="⚠️ Bağlantı koptu, yeniden bağlanılıyor..." type="warning" />,
|
|||
|
|
{ placement: 'top-end' },
|
|||
|
|
)
|
|||
|
|
this.isConnected = false
|
|||
|
|
this.currentSessionId = undefined
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.isConnected = false
|
|||
|
|
try {
|
|||
|
|
if (this.currentSessionId) {
|
|||
|
|
await this.connection.invoke('LeaveClass', this.currentSessionId)
|
|||
|
|
}
|
|||
|
|
} finally {
|
|||
|
|
this.currentSessionId = undefined
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.connection.on('Error', (message: string) => {
|
|||
|
|
toast.push(<Notification title={`❌ Hata: ${message}`} type="danger" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.connection.on('Warning', (message: string) => {
|
|||
|
|
toast.push(<Notification title={`⚠️ Uyarı: ${message}`} type="warning" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.connection.on('Info', (message: string) => {
|
|||
|
|
toast.push(<Notification title={`ℹ️ Bilgi: ${message}`} type="info" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.connection.onreconnecting(() => {
|
|||
|
|
if (this.isKicked) {
|
|||
|
|
toast.push(
|
|||
|
|
<Notification
|
|||
|
|
title="❌ Sınıftan çıkarıldığınız için yeniden bağlanma engellendi"
|
|||
|
|
type="danger"
|
|||
|
|
/>,
|
|||
|
|
)
|
|||
|
|
this.connection.stop()
|
|||
|
|
throw new Error('Reconnect blocked after kick')
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
this.connection.on('ForceDisconnect', async (message: string) => {
|
|||
|
|
this.isKicked = true
|
|||
|
|
toast.push(<Notification title={`❌ Sınıftan çıkarıldınız: ${message}`} type="danger" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
if (this.onForceCleanup) {
|
|||
|
|
this.onForceCleanup()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await this.connection.stop()
|
|||
|
|
} catch {}
|
|||
|
|
|
|||
|
|
this.isConnected = false
|
|||
|
|
|
|||
|
|
if (this.currentSessionId && store.getState().auth.user) {
|
|||
|
|
this.onParticipantLeft?.({
|
|||
|
|
userId: store.getState().auth.user.id,
|
|||
|
|
sessionId: this.currentSessionId,
|
|||
|
|
userName: store.getState().auth.user.name,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.currentSessionId = undefined
|
|||
|
|
window.location.href = ROUTES_ENUM.protected.admin.videoroom.roomList
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async start(): Promise<void> {
|
|||
|
|
try {
|
|||
|
|
const startPromise = this.connection.start()
|
|||
|
|
const timeout = new Promise((_, reject) =>
|
|||
|
|
setTimeout(() => reject(new Error('Bağlantı zaman aşımına uğradı')), 10000),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
await Promise.race([startPromise, timeout])
|
|||
|
|
this.isConnected = true
|
|||
|
|
toast.push(<Notification title="✅ Bağlantı kuruldu" type="success" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
} catch {
|
|||
|
|
toast.push(
|
|||
|
|
<Notification
|
|||
|
|
title="⚠️ Sunucuya bağlanılamadı. Lütfen sayfayı yenileyin veya internet bağlantınızı kontrol edin."
|
|||
|
|
type="danger"
|
|||
|
|
/>,
|
|||
|
|
{ placement: 'top-end' },
|
|||
|
|
)
|
|||
|
|
this.isConnected = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async joinClass(
|
|||
|
|
sessionId: string,
|
|||
|
|
userId: string,
|
|||
|
|
userName: string,
|
|||
|
|
isTeacher: boolean,
|
|||
|
|
isActive: boolean,
|
|||
|
|
): Promise<void> {
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
toast.push(
|
|||
|
|
<Notification
|
|||
|
|
title="⚠️ Bağlantı yok. Sınıfa katılmadan önce bağlantıyı kontrol edin."
|
|||
|
|
type="warning"
|
|||
|
|
/>,
|
|||
|
|
)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.currentSessionId = sessionId
|
|||
|
|
try {
|
|||
|
|
await this.connection.invoke('JoinClass', sessionId, userId, userName, isTeacher, isActive)
|
|||
|
|
} catch {
|
|||
|
|
toast.push(<Notification title="❌ Sınıfa katılamadı" type="danger" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async leaveClass(sessionId: string): Promise<void> {
|
|||
|
|
const { auth } = store.getState()
|
|||
|
|
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
this.onParticipantLeft?.({ userId: auth.user.id, sessionId, userName: auth.user.name })
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await this.connection.invoke('LeaveClass', sessionId)
|
|||
|
|
this.currentSessionId = undefined
|
|||
|
|
} catch {
|
|||
|
|
toast.push(<Notification title="⚠️ Çıkış başarısız" type="warning" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async sendChatMessage(
|
|||
|
|
sessionId: string,
|
|||
|
|
senderId: string,
|
|||
|
|
senderName: string,
|
|||
|
|
message: string,
|
|||
|
|
isTeacher: boolean,
|
|||
|
|
): Promise<void> {
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
const chatMessage: VideoroomChatDto = {
|
|||
|
|
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 {
|
|||
|
|
toast.push(<Notification title="❌ Mesaj gönderilemedi" type="danger" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async sendPrivateMessage(
|
|||
|
|
sessionId: string,
|
|||
|
|
senderId: string,
|
|||
|
|
senderName: string,
|
|||
|
|
message: string,
|
|||
|
|
recipientId: string,
|
|||
|
|
recipientName: string,
|
|||
|
|
isTeacher: boolean,
|
|||
|
|
): Promise<void> {
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
const chatMessage: VideoroomChatDto = {
|
|||
|
|
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 {
|
|||
|
|
toast.push(<Notification title="❌ Özel mesaj gönderilemedi" type="danger" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async sendAnnouncement(
|
|||
|
|
sessionId: string,
|
|||
|
|
senderId: string,
|
|||
|
|
senderName: string,
|
|||
|
|
message: string,
|
|||
|
|
isTeacher: boolean,
|
|||
|
|
): Promise<void> {
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
const chatMessage: VideoroomChatDto = {
|
|||
|
|
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 {
|
|||
|
|
toast.push(<Notification title="❌ Duyuru gönderilemedi" type="danger" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async muteParticipant(
|
|||
|
|
sessionId: string,
|
|||
|
|
userId: string,
|
|||
|
|
isMuted: boolean,
|
|||
|
|
isTeacher: boolean,
|
|||
|
|
): Promise<void> {
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.onParticipantMuted?.(userId, isMuted)
|
|||
|
|
}, 100)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted, isTeacher)
|
|||
|
|
} catch {
|
|||
|
|
toast.push(<Notification title="⚠️ Katılımcı susturulamadı" type="warning" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async raiseHand(sessionId: string, studentId: string, studentName: string): Promise<void> {
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.onHandRaiseReceived?.(studentId)
|
|||
|
|
}, 100)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await this.connection.invoke('RaiseHand', sessionId, studentId, studentName)
|
|||
|
|
} catch {
|
|||
|
|
toast.push(<Notification title="❌ El kaldırma başarısız" type="danger" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async kickParticipant(sessionId: string, participantId: string, userName: string): Promise<void> {
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.onParticipantLeft?.({ userId: participantId, sessionId, userName })
|
|||
|
|
}, 100)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await this.connection.invoke('KickParticipant', sessionId, participantId)
|
|||
|
|
} catch {
|
|||
|
|
toast.push(<Notification title="❌ Katılımcı atılamadı" type="danger" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async approveHandRaise(sessionId: string, studentId: string): Promise<void> {
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.onHandRaiseDismissed?.(studentId)
|
|||
|
|
}, 100)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await this.connection.invoke('ApproveHandRaise', sessionId, studentId)
|
|||
|
|
} catch {
|
|||
|
|
toast.push(<Notification title="⚠️ El kaldırma onayı başarısız" type="warning" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async dismissHandRaise(sessionId: string, studentId: string): Promise<void> {
|
|||
|
|
if (!this.isConnected) {
|
|||
|
|
setTimeout(() => {
|
|||
|
|
this.onHandRaiseDismissed?.(studentId)
|
|||
|
|
}, 100)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
await this.connection.invoke('DismissHandRaise', sessionId, studentId)
|
|||
|
|
} catch {
|
|||
|
|
toast.push(<Notification title="⚠️ El indirme başarısız" type="warning" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async sendOffer(sessionId: string, targetUserId: string, offer: RTCSessionDescriptionInit) {
|
|||
|
|
if (!this.isConnected) return
|
|||
|
|
await this.connection.invoke('SendOffer', sessionId, targetUserId, offer)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async sendAnswer(sessionId: string, targetUserId: string, answer: RTCSessionDescriptionInit) {
|
|||
|
|
if (!this.isConnected) return
|
|||
|
|
await this.connection.invoke('SendAnswer', sessionId, targetUserId, answer)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async sendIceCandidate(sessionId: string, targetUserId: string, candidate: RTCIceCandidateInit) {
|
|||
|
|
if (!this.isConnected) return
|
|||
|
|
await this.connection.invoke('SendIceCandidate', sessionId, targetUserId, candidate)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setExistingParticipantsHandler(callback: (participants: any[]) => void) {
|
|||
|
|
this.connection.on('ExistingParticipants', callback)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setAttendanceUpdatedHandler(callback: (record: VideoroomAttendanceDto) => void) {
|
|||
|
|
this.onAttendanceUpdate = callback
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setParticipantJoinHandler(
|
|||
|
|
callback: (userId: string, name: string, isTeacher: boolean, isActive: boolean) => void,
|
|||
|
|
) {
|
|||
|
|
this.onParticipantJoined = callback
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setParticipantLeaveHandler(
|
|||
|
|
callback: (payload: { userId: string; sessionId: string; userName: string }) => void,
|
|||
|
|
) {
|
|||
|
|
this.onParticipantLeft = callback
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setChatMessageReceivedHandler(callback: (message: VideoroomChatDto) => 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
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setOfferReceivedHandler(
|
|||
|
|
callback: (fromUserId: string, offer: RTCSessionDescriptionInit) => void,
|
|||
|
|
) {
|
|||
|
|
this.onOfferReceived = callback
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setAnswerReceivedHandler(
|
|||
|
|
callback: (fromUserId: string, answer: RTCSessionDescriptionInit) => void,
|
|||
|
|
) {
|
|||
|
|
this.onAnswerReceived = callback
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setIceCandidateReceivedHandler(
|
|||
|
|
callback: (fromUserId: string, candidate: RTCIceCandidateInit) => void,
|
|||
|
|
) {
|
|||
|
|
this.onIceCandidateReceived = callback
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async disconnect(): Promise<void> {
|
|||
|
|
if (this.isConnected && this.currentSessionId) {
|
|||
|
|
try {
|
|||
|
|
await this.connection.invoke('LeaveClass', this.currentSessionId)
|
|||
|
|
} catch {
|
|||
|
|
toast.push(<Notification title="⚠️ Bağlantı koparılırken hata" type="warning" />, {
|
|||
|
|
placement: 'top-end',
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (this.connection) {
|
|||
|
|
await this.connection.stop()
|
|||
|
|
}
|
|||
|
|
this.isConnected = false
|
|||
|
|
this.currentSessionId = undefined
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
getConnectionState(): boolean {
|
|||
|
|
return this.isConnected
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setForceCleanupHandler(callback: () => void) {
|
|||
|
|
this.onForceCleanup = callback
|
|||
|
|
}
|
|||
|
|
}
|