Add Express server with WebSocket, CORS, and room routes
Set up the HTTP server with Express, attach Socket.IO for real-time messaging, add CORS middleware, async error handling utility, and REST routes for room join/leave/messages/participants. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
const { randomUUID } = require('crypto')
|
||||
const { Server } = require('socket.io')
|
||||
|
||||
function createRealtimeServer(server, { clientOrigin, database, onError }) {
|
||||
const io = new Server(server, {
|
||||
cors: {
|
||||
origin: clientOrigin,
|
||||
methods: ['GET', 'POST', 'PATCH', 'DELETE', 'OPTIONS'],
|
||||
},
|
||||
})
|
||||
|
||||
io.use(async (socket, next) => {
|
||||
try {
|
||||
const auth = socket.handshake.auth || {}
|
||||
const query = socket.handshake.query || {}
|
||||
const room = readParam(auth.room, query.room)
|
||||
const userId = readParam(auth.userId, query.userId)
|
||||
|
||||
if (!room || !userId) {
|
||||
return next(new Error('Missing room or userId'))
|
||||
}
|
||||
|
||||
const user = await database.getUser(userId)
|
||||
if (!user || user.room !== room) {
|
||||
return next(new Error('User not authorized for this room'))
|
||||
}
|
||||
|
||||
socket.data.room = room
|
||||
socket.data.userId = userId
|
||||
next()
|
||||
} catch (error) {
|
||||
next(error)
|
||||
}
|
||||
})
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
registerConnection(io, socket, database, onError).catch((error) => {
|
||||
onError('Socket connection setup failed', error)
|
||||
socket.disconnect(true)
|
||||
})
|
||||
})
|
||||
|
||||
return io
|
||||
}
|
||||
|
||||
async function registerConnection(io, socket, database, onError) {
|
||||
const { room, userId } = socket.data
|
||||
socket.join(room)
|
||||
|
||||
const user = await database.setUserOnline(userId, room)
|
||||
if (!user) {
|
||||
socket.disconnect(true)
|
||||
return
|
||||
}
|
||||
|
||||
socket.emit('connection:ready', { userId, room })
|
||||
socket.emit('history:init', await database.getMessages(room))
|
||||
await broadcastParticipants(io, database, room)
|
||||
|
||||
socket.on('message:send', async (payload) => {
|
||||
try {
|
||||
const text =
|
||||
typeof payload?.text === 'string' ? payload.text : payload?.payload?.text
|
||||
if (!text || !text.trim()) {
|
||||
return
|
||||
}
|
||||
|
||||
const activeUser = await database.getUser(userId)
|
||||
if (!activeUser || activeUser.room !== room) {
|
||||
return
|
||||
}
|
||||
|
||||
const message = await database.addMessage({
|
||||
id: randomUUID(),
|
||||
room,
|
||||
userId,
|
||||
username: activeUser.username,
|
||||
text: text.trim(),
|
||||
})
|
||||
io.to(room).emit('message:new', message)
|
||||
} catch (error) {
|
||||
onError('Failed to handle socket message', error)
|
||||
}
|
||||
})
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
database
|
||||
.setUserOffline(userId)
|
||||
.then(() => broadcastParticipants(io, database, room))
|
||||
.catch((error) => onError('Failed to update presence', error))
|
||||
})
|
||||
|
||||
socket.on('error', (error) => {
|
||||
onError('Socket error', error)
|
||||
})
|
||||
}
|
||||
|
||||
async function broadcastParticipants(io, database, room) {
|
||||
const participants = await database.getParticipants(room)
|
||||
io.to(room).emit('participants:update', participants)
|
||||
}
|
||||
|
||||
function readParam(primary, fallback) {
|
||||
if (typeof primary === 'string' && primary.trim()) return primary
|
||||
if (typeof fallback === 'string' && fallback.trim()) return fallback
|
||||
return null
|
||||
}
|
||||
|
||||
module.exports = createRealtimeServer
|
||||
Reference in New Issue
Block a user