aethex.live/lib/socket.ts

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
}