Scaffold and Phase 1

This commit is contained in:
2026-05-02 19:46:42 -05:00
parent ab74e7cad4
commit d909cb7c30
92 changed files with 4967 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
import type { Server } from "socket.io";
import type {
ServerToClientEvents,
ClientToServerEvents,
InterServerEvents,
SocketData,
} from "@storybid/shared";
import { registerLiveAuctionHandlers } from "./live-auction.js";
import { registerSilentAuctionHandlers } from "./silent-auction.js";
import { verifyToken } from "../lib/jwt.js";
type IO = Server<ClientToServerEvents, ServerToClientEvents, InterServerEvents, SocketData>;
export function registerSocketHandlers(io: IO): void {
// Auth middleware validate JWT on handshake
io.use((socket, next) => {
const token =
(socket.handshake.auth["token"] as string | undefined) ??
(socket.handshake.headers["authorization"] as string | undefined)?.replace("Bearer ", "");
if (!token) {
// Allow unauthenticated connections for display board / public catalog
return next();
}
try {
const payload = verifyToken(token);
socket.data.bidderId = payload.role === "bidder" ? payload.sub : undefined;
socket.data.staffId = payload.role !== "bidder" ? payload.sub : undefined;
socket.data.role = payload.role;
socket.data.deviceId = payload.deviceId;
} catch {
return next(new Error("Invalid token"));
}
next();
});
io.on("connection", (socket) => {
console.log(`[socket] connected ${socket.id} role=${socket.data.role ?? "guest"}`);
// Auto-join personal room for outbid / checkout notifications
if (socket.data.bidderId) {
void socket.join(`bidder:${socket.data.bidderId}`);
}
// Room join/leave for event-scoped broadcasts
socket.on("join_event", (eventId) => {
void socket.join(`event:${eventId}`);
});
socket.on("leave_event", (eventId) => {
void socket.leave(`event:${eventId}`);
});
registerLiveAuctionHandlers(io, socket);
registerSilentAuctionHandlers(io, socket);
socket.on("disconnect", (reason) => {
console.log(`[socket] disconnected ${socket.id} reason=${reason}`);
});
});
}