138 lines
3.9 KiB
TypeScript
138 lines
3.9 KiB
TypeScript
import { Server } from 'socket.io'
|
|
import { PrismaClient } from '@prisma/client'
|
|
import type { Server as HTTPServer } from 'http'
|
|
|
|
const prisma = new PrismaClient()
|
|
|
|
export function setupSocketServer(httpServer: HTTPServer) {
|
|
const io = new Server(httpServer, {
|
|
cors: {
|
|
origin: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
|
|
methods: ['GET', 'POST'],
|
|
credentials: true,
|
|
},
|
|
})
|
|
|
|
io.on('connection', (socket) => {
|
|
console.log('Client connected:', socket.id)
|
|
|
|
// Join a stream room
|
|
socket.on('join:stream', ({ streamId, userId }) => {
|
|
console.log(`User ${userId} joined stream ${streamId}`)
|
|
socket.join(`stream:${streamId}`)
|
|
|
|
// Broadcast viewer count update
|
|
const roomSize = io.sockets.adapter.rooms.get(`stream:${streamId}`)?.size || 0
|
|
io.to(`stream:${streamId}`).emit('viewers:update', {
|
|
streamId,
|
|
viewerCount: roomSize,
|
|
})
|
|
|
|
// Update viewer count in database (throttled)
|
|
prisma.stream
|
|
.update({
|
|
where: { id: streamId },
|
|
data: { viewerCount: roomSize },
|
|
})
|
|
.catch(console.error)
|
|
})
|
|
|
|
// Leave stream room
|
|
socket.on('leave:stream', ({ streamId, userId }) => {
|
|
console.log(`User ${userId} left stream ${streamId}`)
|
|
socket.leave(`stream:${streamId}`)
|
|
|
|
// Broadcast viewer count update
|
|
const roomSize = io.sockets.adapter.rooms.get(`stream:${streamId}`)?.size || 0
|
|
io.to(`stream:${streamId}`).emit('viewers:update', {
|
|
streamId,
|
|
viewerCount: roomSize,
|
|
})
|
|
|
|
// Update viewer count in database
|
|
prisma.stream
|
|
.update({
|
|
where: { id: streamId },
|
|
data: { viewerCount: roomSize },
|
|
})
|
|
.catch(console.error)
|
|
})
|
|
|
|
// Send chat message
|
|
socket.on('chat:message', async ({ streamId, userId, message }) => {
|
|
try {
|
|
// Get user details
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: {
|
|
id: true,
|
|
displayName: true,
|
|
avatarUrl: true,
|
|
verified: true,
|
|
isCreator: true,
|
|
},
|
|
})
|
|
|
|
if (!user) {
|
|
socket.emit('chat:error', { message: 'User not found' })
|
|
return
|
|
}
|
|
|
|
// Save message to database
|
|
const chatMessage = await prisma.chatMessage.create({
|
|
data: {
|
|
streamId,
|
|
userId,
|
|
message: message.trim(),
|
|
badges: user.isCreator ? ['creator'] : [],
|
|
},
|
|
})
|
|
|
|
// Broadcast to all in room
|
|
io.to(`stream:${streamId}`).emit('chat:message', {
|
|
id: chatMessage.id,
|
|
message: chatMessage.message,
|
|
badges: chatMessage.badges,
|
|
user: {
|
|
id: user.id,
|
|
displayName: user.displayName,
|
|
avatarUrl: user.avatarUrl,
|
|
verified: user.verified,
|
|
},
|
|
createdAt: chatMessage.createdAt,
|
|
})
|
|
} catch (error) {
|
|
console.error('Error sending chat message:', error)
|
|
socket.emit('chat:error', { message: 'Failed to send message' })
|
|
}
|
|
})
|
|
|
|
// Delete chat message (moderator only)
|
|
socket.on('chat:delete', async ({ messageId, userId }) => {
|
|
try {
|
|
// TODO: Check if user is a moderator
|
|
await prisma.chatMessage.update({
|
|
where: { id: messageId },
|
|
data: { isDeleted: true, deletedReason: 'Deleted by moderator' },
|
|
})
|
|
|
|
// Notify all clients
|
|
io.emit('chat:deleted', { messageId })
|
|
} catch (error) {
|
|
console.error('Error deleting chat message:', error)
|
|
}
|
|
})
|
|
|
|
// Notification for donations
|
|
socket.on('donation:alert', ({ streamId, donation }) => {
|
|
io.to(`stream:${streamId}`).emit('donation:received', donation)
|
|
})
|
|
|
|
// Disconnect
|
|
socket.on('disconnect', () => {
|
|
console.log('Client disconnected:', socket.id)
|
|
})
|
|
})
|
|
|
|
return io
|
|
}
|