Initial commit
This commit is contained in:
@@ -9,6 +9,11 @@ function createRealtimeServer(server, { clientOrigin, database, onError }) {
|
||||
},
|
||||
})
|
||||
|
||||
const typingByRoom = new Map()
|
||||
|
||||
// io.use: Middleware die bei jeder eingehenden WebSocket-Verbindung läuft.
|
||||
// Prüft ob room und userId im Handshake vorhanden sind und ob der User in der DB existiert.
|
||||
// Wer vorher nicht POST /join aufgerufen hat, hat keine gültige userId und wird abgelehnt.
|
||||
io.use(async (socket, next) => {
|
||||
try {
|
||||
const auth = socket.handshake.auth || {}
|
||||
@@ -25,6 +30,7 @@ function createRealtimeServer(server, { clientOrigin, database, onError }) {
|
||||
return next(new Error('User not authorized for this room'))
|
||||
}
|
||||
|
||||
// room und userId in socket.data speichern — so sind sie in registerConnection verfügbar.
|
||||
socket.data.room = room
|
||||
socket.data.userId = userId
|
||||
next()
|
||||
@@ -34,7 +40,7 @@ function createRealtimeServer(server, { clientOrigin, database, onError }) {
|
||||
})
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
registerConnection(io, socket, database, onError).catch((error) => {
|
||||
registerConnection(io, socket, database, onError, typingByRoom).catch((error) => {
|
||||
onError('Socket connection setup failed', error)
|
||||
socket.disconnect(true)
|
||||
})
|
||||
@@ -43,8 +49,10 @@ function createRealtimeServer(server, { clientOrigin, database, onError }) {
|
||||
return io
|
||||
}
|
||||
|
||||
async function registerConnection(io, socket, database, onError) {
|
||||
async function registerConnection(io, socket, database, onError, typingByRoom) {
|
||||
const { room, userId } = socket.data
|
||||
|
||||
// socket.join: Fügt den Socket einem Room-Kanal hinzu — io.to(room).emit() erreicht nur diese Sockets.
|
||||
socket.join(room)
|
||||
|
||||
const user = await database.setUserOnline(userId, room)
|
||||
@@ -53,10 +61,25 @@ async function registerConnection(io, socket, database, onError) {
|
||||
return
|
||||
}
|
||||
|
||||
socket.data.username = user.username
|
||||
|
||||
socket.emit('connection:ready', { userId, room })
|
||||
socket.emit('history:init', await database.getMessages(room))
|
||||
|
||||
// Teilnehmerliste an alle im Room senden — User sieht sofort wer online ist.
|
||||
await broadcastParticipants(io, database, room)
|
||||
|
||||
// typingByRoom: Map pro Room — speichert wer gerade tippt { userId → { userId, username } }.
|
||||
socket.on('typing:start', () => {
|
||||
if (!typingByRoom.has(room)) typingByRoom.set(room, new Map())
|
||||
typingByRoom.get(room).set(userId, { userId, username: socket.data.username })
|
||||
io.to(room).emit('typing:update', [...typingByRoom.get(room).values()])
|
||||
})
|
||||
|
||||
socket.on('typing:stop', () => {
|
||||
typingByRoom.get(room)?.delete(userId)
|
||||
io.to(room).emit('typing:update', [...(typingByRoom.get(room)?.values() ?? [])])
|
||||
})
|
||||
|
||||
socket.on('message:send', async (payload) => {
|
||||
try {
|
||||
const text =
|
||||
@@ -65,11 +88,16 @@ async function registerConnection(io, socket, database, onError) {
|
||||
return
|
||||
}
|
||||
|
||||
// Nochmal prüfen ob der User noch im richtigen Room ist — könnte sich seit dem Verbinden geändert haben.
|
||||
const activeUser = await database.getUser(userId)
|
||||
if (!activeUser || activeUser.room !== room) {
|
||||
return
|
||||
}
|
||||
|
||||
// Tipp-Indikator sofort entfernen wenn eine Nachricht gesendet wird.
|
||||
typingByRoom.get(room)?.delete(userId)
|
||||
io.to(room).emit('typing:update', [...(typingByRoom.get(room)?.values() ?? [])])
|
||||
|
||||
const message = await database.addMessage({
|
||||
id: randomUUID(),
|
||||
room,
|
||||
@@ -77,6 +105,7 @@ async function registerConnection(io, socket, database, onError) {
|
||||
username: activeUser.username,
|
||||
text: text.trim(),
|
||||
})
|
||||
// Nachricht an alle im Room senden — inkl. Sender selbst.
|
||||
io.to(room).emit('message:new', message)
|
||||
} catch (error) {
|
||||
onError('Failed to handle socket message', error)
|
||||
@@ -84,6 +113,9 @@ async function registerConnection(io, socket, database, onError) {
|
||||
})
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
typingByRoom.get(room)?.delete(userId)
|
||||
io.to(room).emit('typing:update', [...(typingByRoom.get(room)?.values() ?? [])])
|
||||
// User als offline markieren und Teilnehmerliste aktualisieren.
|
||||
database
|
||||
.setUserOffline(userId)
|
||||
.then(() => broadcastParticipants(io, database, room))
|
||||
@@ -100,6 +132,8 @@ async function broadcastParticipants(io, database, room) {
|
||||
io.to(room).emit('participants:update', participants)
|
||||
}
|
||||
|
||||
// Liest einen Parameter zuerst aus der primären Quelle (auth), dann aus dem Fallback (query).
|
||||
// Schützt gegen leere Strings — nur echte Werte werden akzeptiert.
|
||||
function readParam(primary, fallback) {
|
||||
if (typeof primary === 'string' && primary.trim()) return primary
|
||||
if (typeof fallback === 'string' && fallback.trim()) return fallback
|
||||
|
||||
Reference in New Issue
Block a user