/** * Custom server for Socket.io integration with Next.js * Run this with `node server.js` for real-time chat support */ const { createServer } = require('http') const { parse } = require('url') const next = require('next') const { Server } = require('socket.io') const dev = process.env.NODE_ENV !== 'production' const hostname = 'localhost' const port = parseInt(process.env.PORT, 10) || 3000 const app = next({ dev, hostname, port }) const handle = app.getRequestHandler() app.prepare().then(() => { const httpServer = createServer((req, res) => { const parsedUrl = parse(req.url, true) handle(req, res, parsedUrl) }) // Setup Socket.io on the HTTP server const io = new Server(httpServer, { cors: { origin: process.env.NEXT_PUBLIC_APP_URL || `http://${hostname}:${port}`, methods: ['GET', 'POST'], credentials: true, }, }) // Import Prisma Client const { PrismaClient } = require('@prisma/client') const prisma = new PrismaClient() 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 const roomSize = io.sockets.adapter.rooms.get(`stream:${streamId}`)?.size || 0 io.to(`stream:${streamId}`).emit('viewers:update', { streamId, viewerCount: roomSize, }) // Update DB prisma.stream .update({ where: { id: streamId }, data: { viewerCount: roomSize }, }) .catch(console.error) }) // Leave stream socket.on('leave:stream', ({ streamId, userId }) => { socket.leave(`stream:${streamId}`) const roomSize = io.sockets.adapter.rooms.get(`stream:${streamId}`)?.size || 0 io.to(`stream:${streamId}`).emit('viewers:update', { streamId, viewerCount: roomSize, }) prisma.stream .update({ where: { id: streamId }, data: { viewerCount: roomSize }, }) .catch(console.error) }) // Chat message socket.on('chat:message', async ({ streamId, userId, message }) => { try { const user = await prisma.user.findUnique({ where: { id: userId }, select: { id: true, displayName: true, avatarUrl: true, verified: true, isCreator: true, }, }) if (!user) return socket.emit('chat:error', { message: 'User not found' }) const chatMessage = await prisma.chatMessage.create({ data: { streamId, userId, message: message.trim(), badges: user.isCreator ? ['creator'] : [], }, }) 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('Chat error:', error) } }) socket.on('disconnect', () => { console.log('Client disconnected:', socket.id) }) }) httpServer.listen(port, () => { console.log(`> Ready on http://${hostname}:${port}`) console.log(`> Socket.io server running`) }) })