Add real-time booking collaboration via WebSocket
- Backend: /ws relay endpoint (@fastify/websocket) - Frontend: useHotelSocket hook with lock/unlock/booking events - Calendar: lock overlay with diagonal stripes shows other manager editing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1751
backend/package-lock.json
generated
Normal file
1751
backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,7 @@
|
||||
"@fastify/helmet": "^11.1.1",
|
||||
"@fastify/jwt": "^8.0.1",
|
||||
"@fastify/rate-limit": "^9.1.0",
|
||||
"@fastify/websocket": "^11.2.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"fastify": "^4.28.1",
|
||||
"ioredis": "^5.3.2",
|
||||
|
||||
@@ -7,6 +7,7 @@ import rateLimit from '@fastify/rate-limit'
|
||||
import { config } from './config'
|
||||
import './types' // side-effect: augments fastify types
|
||||
|
||||
import wsRoutes from './routes/ws'
|
||||
import authRoutes from './routes/auth'
|
||||
import hotelsRoutes from './routes/hotels'
|
||||
import roomsRoutes from './routes/rooms'
|
||||
@@ -62,6 +63,7 @@ export async function buildApp() {
|
||||
fastify.get('/health', async () => ({ status: 'ok', ts: new Date().toISOString() }))
|
||||
|
||||
// ── Routes ─────────────────────────────────────────────────────────────────
|
||||
await fastify.register(wsRoutes)
|
||||
await fastify.register(authRoutes)
|
||||
await fastify.register(hotelsRoutes)
|
||||
await fastify.register(roomsRoutes)
|
||||
|
||||
40
backend/src/routes/ws.ts
Normal file
40
backend/src/routes/ws.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { FastifyPluginAsync } from 'fastify'
|
||||
import fastifyWebsocket from '@fastify/websocket'
|
||||
import type { WebSocket } from 'ws'
|
||||
|
||||
// hotel slug → set of connected clients
|
||||
const hotelRooms = new Map<string, Set<WebSocket>>()
|
||||
|
||||
const ws: FastifyPluginAsync = async (fastify) => {
|
||||
await fastify.register(fastifyWebsocket)
|
||||
|
||||
fastify.get<{ Querystring: { hotel?: string; token?: string } }>(
|
||||
'/ws',
|
||||
{ websocket: true },
|
||||
(socket, request) => {
|
||||
const hotel = request.query.hotel ?? ''
|
||||
if (!hotel) { socket.close(4001, 'hotel required'); return }
|
||||
|
||||
if (!hotelRooms.has(hotel)) hotelRooms.set(hotel, new Set())
|
||||
hotelRooms.get(hotel)!.add(socket)
|
||||
|
||||
socket.on('message', (raw) => {
|
||||
const data = raw.toString()
|
||||
const peers = hotelRooms.get(hotel)
|
||||
if (!peers) return
|
||||
peers.forEach(client => {
|
||||
if (client !== socket && client.readyState === 1) {
|
||||
client.send(data)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
socket.on('close', () => {
|
||||
hotelRooms.get(hotel)?.delete(socket)
|
||||
if (hotelRooms.get(hotel)?.size === 0) hotelRooms.delete(hotel)
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
export default ws
|
||||
Reference in New Issue
Block a user