@air-jam/server 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -697,6 +697,48 @@ io.on(
697
697
  loop
698
698
  });
699
699
  });
700
+ socket.on("host:state_sync", (payload) => {
701
+ const { roomId, data } = payload;
702
+ const session = roomManager.getRoom(roomId);
703
+ if (!session) {
704
+ return;
705
+ }
706
+ if (session.masterHostSocketId !== socket.id && session.childHostSocketId !== socket.id) {
707
+ return;
708
+ }
709
+ const syncPayload = {
710
+ roomId,
711
+ data
712
+ };
713
+ io.to(roomId).emit("airjam:state_sync", syncPayload);
714
+ });
715
+ socket.on(
716
+ "controller:action_rpc",
717
+ (payload) => {
718
+ const { roomId, actionName, args, controllerId } = payload;
719
+ const session = roomManager.getRoom(roomId);
720
+ if (!session) return;
721
+ const controllerSession = session.controllers.get(controllerId);
722
+ if (!controllerSession) {
723
+ return;
724
+ }
725
+ if (controllerSession.socketId !== socket.id) {
726
+ roomManager.deleteController(controllerSession.socketId);
727
+ controllerSession.socketId = socket.id;
728
+ roomManager.setController(socket.id, { roomId, controllerId });
729
+ socket.join(roomId);
730
+ }
731
+ const hostId = roomManager.getActiveHostId(session);
732
+ if (hostId) {
733
+ const rpcPayload = {
734
+ actionName,
735
+ args,
736
+ controllerId
737
+ };
738
+ io.to(hostId).emit("airjam:action_rpc", rpcPayload);
739
+ }
740
+ }
741
+ );
700
742
  socket.on("disconnect", () => {
701
743
  const roomId = roomManager.getRoomByHostId(socket.id);
702
744
  if (roomId) {
@@ -746,4 +788,4 @@ io.on(
746
788
  httpServer.listen(PORT, () => {
747
789
  console.log(`[air-jam] server listening on http://localhost:${PORT}`);
748
790
  });
749
- //# sourceMappingURL=chunk-NWAVO5AJ.js.map
791
+ //# sourceMappingURL=chunk-KMAEESOE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/services/auth-service.ts","../src/db.ts","../src/services/room-manager.ts"],"sourcesContent":["import {\n controllerInputSchema,\n controllerJoinSchema,\n controllerLeaveSchema,\n controllerStateSchema,\n controllerSystemSchema,\n ErrorCode,\n hostJoinAsChildSchema,\n hostRegisterSystemSchema,\n hostRegistrationSchema,\n PlaySoundEventPayload,\n SignalPayload,\n systemLaunchGameSchema,\n type AirJamActionRpcPayload,\n type AirJamStateSyncPayload,\n type ClientToServerEvents,\n type ControllerActionRpcPayload,\n type ControllerInputEvent,\n type ControllerJoinedNotice,\n type ControllerJoinPayload,\n type ControllerLeavePayload,\n type ControllerLeftNotice,\n type ControllerStateMessage,\n type HostJoinAsChildPayload,\n type HostRegisterSystemPayload,\n type HostRegistrationPayload,\n type HostStateSyncPayload,\n type InterServerEvents,\n type PlayerProfile,\n type ServerErrorPayload,\n type ServerToClientEvents,\n type SocketData,\n type SystemLaunchGamePayload,\n} from \"@air-jam/sdk/protocol\";\nimport Color from \"color\";\nimport cors from \"cors\";\nimport express from \"express\";\nimport { createServer } from \"node:http\";\nimport { Server, type Socket } from \"socket.io\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { authService } from \"./services/auth-service.js\";\nimport { roomManager } from \"./services/room-manager.js\";\nimport type { ControllerSession } from \"./types.js\";\n\nconst PORT = Number(process.env.PORT ?? 4000);\n\nconst app = express();\napp.use(cors());\napp.use(express.json());\n\napp.get(\"/health\", (_, res) => {\n res.json({ ok: true });\n});\n\nconst httpServer = createServer(app);\n\nconst io = new Server<\n ClientToServerEvents,\n ServerToClientEvents,\n InterServerEvents,\n SocketData\n>(httpServer, {\n cors: {\n origin: \"*\",\n },\n pingInterval: 2000,\n pingTimeout: 5000,\n});\n\nconst emitError = (socketId: string, payload: ServerErrorPayload): void => {\n io.to(socketId).emit(\"server:error\", payload);\n};\n\nio.on(\n \"connection\",\n (\n socket: Socket<\n ClientToServerEvents,\n ServerToClientEvents,\n InterServerEvents,\n SocketData\n >,\n ) => {\n // --- SYSTEM HOST REGISTRATION (Arcade) ---\n socket.on(\n \"host:registerSystem\",\n async (payload: HostRegisterSystemPayload, callback) => {\n const parsed = hostRegisterSystemSchema.safeParse(payload);\n if (!parsed.success) {\n callback({\n ok: false,\n message: parsed.error.message,\n code: ErrorCode.INVALID_PAYLOAD,\n });\n return;\n }\n\n const { roomId, apiKey } = parsed.data;\n\n // API Key Validation using authService\n const verification = await authService.verifyApiKey(apiKey);\n if (!verification.isVerified) {\n console.warn(\n `[server] Unauthorized host registration attempt for room ${roomId}`,\n );\n callback({\n ok: false,\n message: verification.error,\n code: ErrorCode.INVALID_API_KEY,\n });\n return;\n }\n\n let session = roomManager.getRoom(roomId);\n\n if (session) {\n // Reconnect logic for System Host\n session.masterHostSocketId = socket.id;\n roomManager.setRoom(roomId, session);\n } else {\n // Create new room\n console.log(`[server] Creating room ${roomId}`);\n session = {\n roomId,\n masterHostSocketId: socket.id,\n focus: \"SYSTEM\",\n controllers: new Map(),\n maxPlayers: 32, // Default increased to 32 to allow for observers/queue\n gameState: \"paused\",\n };\n roomManager.setRoom(roomId, session);\n }\n\n roomManager.setHostRoom(socket.id, roomId);\n socket.join(roomId);\n\n callback({ ok: true, roomId });\n io.to(roomId).emit(\"server:roomReady\", { roomId });\n },\n );\n\n // --- LAUNCH GAME (System -> Server) ---\n socket.on(\n \"system:launchGame\",\n (payload: SystemLaunchGamePayload, callback) => {\n const parsed = systemLaunchGameSchema.safeParse(payload);\n if (!parsed.success) {\n callback({\n ok: false,\n message: parsed.error.message,\n code: ErrorCode.INVALID_PAYLOAD,\n });\n return;\n }\n\n const { roomId, gameUrl } = parsed.data;\n const session = roomManager.getRoom(roomId);\n\n if (!session) {\n callback({\n ok: false,\n message: \"Room not found\",\n code: ErrorCode.ROOM_NOT_FOUND,\n });\n return;\n }\n\n if (session.masterHostSocketId !== socket.id) {\n callback({\n ok: false,\n message: \"Unauthorized: Not System Host\",\n code: ErrorCode.UNAUTHORIZED,\n });\n return;\n }\n\n // Check if game is already active\n if (session.childHostSocketId) {\n callback({\n ok: false,\n message: \"Game already active\",\n code: ErrorCode.ALREADY_CONNECTED,\n });\n return;\n }\n\n // Check if a launch is already in progress (joinToken exists but child hasn't joined yet)\n if (session.joinToken && !session.childHostSocketId) {\n callback({ ok: true, joinToken: session.joinToken });\n return;\n }\n\n // Generate Join Token\n const joinToken = uuidv4();\n session.joinToken = joinToken;\n session.activeControllerUrl = gameUrl;\n\n console.log(`[server] Launching game in room ${roomId}`);\n\n // Broadcast to controllers to load UI\n io.to(roomId).emit(\"client:loadUi\", { url: gameUrl });\n\n callback({ ok: true, joinToken });\n },\n );\n\n // --- JOIN AS CHILD (Game -> Server) ---\n socket.on(\n \"host:joinAsChild\",\n (payload: HostJoinAsChildPayload, callback) => {\n const parsed = hostJoinAsChildSchema.safeParse(payload);\n if (!parsed.success) {\n callback({\n ok: false,\n message: parsed.error.message,\n code: ErrorCode.INVALID_PAYLOAD,\n });\n return;\n }\n\n const { roomId, joinToken } = parsed.data;\n const session = roomManager.getRoom(roomId);\n\n if (!session) {\n callback({\n ok: false,\n message: \"Room not found\",\n code: ErrorCode.ROOM_NOT_FOUND,\n });\n return;\n }\n\n if (session.joinToken !== joinToken) {\n console.warn(\n `[server] Invalid join token for room ${roomId}. Expected ${session.joinToken}, got ${joinToken}`,\n );\n callback({\n ok: false,\n message: \"Invalid Join Token\",\n code: ErrorCode.INVALID_TOKEN,\n });\n return;\n }\n\n console.log(`[server] Game host joined room ${roomId}`);\n session.childHostSocketId = socket.id;\n session.focus = \"GAME\"; // Auto-focus on join\n\n roomManager.setHostRoom(socket.id, roomId);\n socket.join(roomId);\n\n // Send initial state to the game\n\n // Small delay to ensure client is ready to receive events after ack\n setTimeout(() => {\n session.controllers.forEach((c) => {\n const notice: ControllerJoinedNotice = {\n controllerId: c.controllerId,\n nickname: c.nickname,\n player: c.playerProfile,\n };\n socket.emit(\"server:controllerJoined\", notice);\n });\n\n // Send current game state to the child host\n const statePayload = {\n roomId,\n state: {\n gameState: session.gameState,\n },\n };\n socket.emit(\"server:state\", statePayload);\n }, 100);\n\n callback({ ok: true, roomId });\n },\n );\n\n // --- CLOSE GAME (System -> Server) ---\n socket.on(\"system:closeGame\", (payload: { roomId: string }) => {\n const { roomId } = payload;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n return;\n }\n\n if (session.masterHostSocketId !== socket.id) {\n return;\n }\n\n console.log(`[server] Closing game in room ${roomId}`);\n\n // Disconnect child host if still connected\n if (session.childHostSocketId) {\n const childSocket = io.sockets.sockets.get(session.childHostSocketId);\n if (childSocket) {\n childSocket.disconnect(true);\n }\n }\n\n session.focus = \"SYSTEM\";\n session.childHostSocketId = undefined;\n session.joinToken = undefined;\n session.activeControllerUrl = undefined;\n\n // Tell controllers to unload UI\n io.to(roomId).emit(\"client:unloadUi\");\n });\n\n // --- LEGACY/STANDALONE HOST REGISTER ---\n // Keeping this for standalone development where there is no \"System\"\n socket.on(\n \"host:register\",\n async (payload: HostRegistrationPayload, callback) => {\n const parsed = hostRegistrationSchema.safeParse(payload);\n if (!parsed.success) {\n callback({\n ok: false,\n message: parsed.error.message,\n code: ErrorCode.INVALID_PAYLOAD,\n });\n return;\n }\n const { roomId, maxPlayers } = parsed.data;\n\n // If mode is 'child', we should redirect them to use host:join_as_child if possible,\n // but for standalone dev, they might use this.\n // For now, we treat 'host:register' as creating a STANDALONE room or joining as master.\n\n let session = roomManager.getRoom(roomId);\n if (session) {\n // If room exists, we assume they are taking over or reconnecting as Master\n session.masterHostSocketId = socket.id;\n session.focus = \"SYSTEM\"; // Default to system/master focus\n } else {\n console.log(`[server] Creating standalone room ${roomId}`);\n session = {\n roomId,\n masterHostSocketId: socket.id,\n focus: \"SYSTEM\",\n controllers: new Map(),\n maxPlayers,\n gameState: \"paused\",\n };\n roomManager.setRoom(roomId, session);\n }\n\n roomManager.setHostRoom(socket.id, roomId);\n socket.join(roomId);\n callback({ ok: true, roomId });\n io.to(roomId).emit(\"server:roomReady\", { roomId });\n },\n );\n\n socket.on(\"controller:join\", (payload: ControllerJoinPayload, callback) => {\n const parsed = controllerJoinSchema.safeParse(payload);\n if (!parsed.success) {\n callback({\n ok: false,\n message: parsed.error.message,\n code: ErrorCode.INVALID_PAYLOAD,\n });\n return;\n }\n const { roomId, controllerId, nickname } = parsed.data;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n callback({\n ok: false,\n message: \"Room not found\",\n code: ErrorCode.ROOM_NOT_FOUND,\n });\n emitError(socket.id, {\n code: ErrorCode.ROOM_NOT_FOUND,\n message: \"Room not found\",\n });\n return;\n }\n\n // When a controller joins, we usually check maxPlayers.\n // However, for the ARCADE room, we want to allow MORE players than the game might support,\n // so they can queue up or watch.\n // But the current logic enforces `session.maxPlayers`.\n // If we want to allow \"observers\" or \"queue\", we should increase maxPlayers for the Arcade room itself.\n // The GAME itself (Child Host) might enforce its own player limit logic by ignoring inputs from extra players.\n\n // For now, let's keep the hard limit on the Room but maybe bump the default.\n if (session.controllers.size >= session.maxPlayers) {\n callback({\n ok: false,\n message: \"Room full\",\n code: ErrorCode.ROOM_FULL,\n });\n emitError(socket.id, {\n code: ErrorCode.ROOM_FULL,\n message: \"Room is full\",\n });\n return;\n }\n\n const existing = session.controllers.get(controllerId);\n if (existing) {\n roomManager.deleteController(existing.socketId);\n }\n\n const PLAYER_COLORS = [\n \"#38bdf8\",\n \"#a78bfa\",\n \"#f472b6\",\n \"#34d399\",\n \"#fbbf24\",\n \"#60a5fa\",\n \"#c084fc\",\n \"#fb7185\",\n \"#4ade80\",\n \"#f87171\",\n \"#22d3ee\",\n \"#a855f7\",\n \"#ec4899\",\n \"#10b981\",\n \"#f59e0b\",\n \"#3b82f6\",\n \"#8b5cf6\",\n \"#ef4444\",\n \"#14b8a6\",\n \"#f97316\",\n ];\n const colorHex =\n PLAYER_COLORS[session.controllers.size % PLAYER_COLORS.length];\n\n let color: string;\n try {\n color = Color(colorHex).hex();\n } catch {\n color = Color(\"#38bdf8\").hex();\n }\n\n const playerProfile: PlayerProfile = {\n id: controllerId,\n label: nickname ?? `Player ${session.controllers.size}`,\n color,\n };\n\n const controllerSession: ControllerSession = {\n controllerId,\n nickname,\n socketId: socket.id,\n playerProfile,\n };\n\n session.controllers.set(controllerId, controllerSession);\n roomManager.setController(socket.id, { roomId, controllerId });\n socket.join(roomId);\n\n const notice: ControllerJoinedNotice = {\n controllerId,\n nickname,\n player: playerProfile,\n };\n\n // Emit to Active Host based on Focus\n io.to(roomManager.getActiveHostId(session)).emit(\n \"server:controllerJoined\",\n notice,\n );\n\n callback({ ok: true, controllerId, roomId });\n\n const welcomePayload = {\n controllerId,\n roomId,\n player: playerProfile,\n };\n\n socket.emit(\"server:welcome\", welcomePayload);\n\n // Send current game state to the new controller\n const statePayload = {\n roomId,\n state: {\n gameState: session.gameState,\n },\n };\n socket.emit(\"server:state\", statePayload);\n\n // IMPORTANT: If a game is already active (activeControllerUrl set),\n // we must tell the new controller to load the game UI immediately.\n // We check activeControllerUrl instead of childHostSocketId because the game might be\n // in the process of loading (launched but not yet connected) or momentarily disconnected.\n if (session.activeControllerUrl) {\n socket.emit(\"client:loadUi\", { url: session.activeControllerUrl });\n }\n\n console.log(\n `[server] Controller joined room ${roomId} (${session.controllers.size}/${session.maxPlayers} players)`,\n );\n });\n\n socket.on(\"controller:leave\", (payload: ControllerLeavePayload) => {\n const parsed = controllerLeaveSchema.safeParse(payload);\n if (!parsed.success) {\n return;\n }\n const { roomId, controllerId } = parsed.data;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n return;\n }\n session.controllers.delete(controllerId);\n roomManager.deleteController(socket.id);\n const notice: ControllerLeftNotice = { controllerId };\n io.to(roomManager.getActiveHostId(session)).emit(\n \"server:controllerLeft\",\n notice,\n );\n socket.leave(roomId);\n });\n\n socket.on(\"controller:input\", (payload: ControllerInputEvent) => {\n // Validate roomId and controllerId, but accept arbitrary input structure\n const result = controllerInputSchema.safeParse(payload);\n if (!result.success) {\n return;\n }\n\n const { roomId } = result.data;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n return;\n }\n\n // Route based on FOCUS - pass through arbitrary input to host\n const targetHostId = roomManager.getActiveHostId(session);\n if (targetHostId) {\n io.to(targetHostId).emit(\"server:input\", result.data);\n }\n });\n\n socket.on(\"controller:system\", (payload) => {\n const parsed = controllerSystemSchema.safeParse(payload);\n if (!parsed.success) {\n return;\n }\n\n const { roomId, command } = parsed.data;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n return;\n }\n\n if (command === \"exit\") {\n // Controller wants to exit the game - close the game on the server\n console.log(`[server] Controller exit request in room ${roomId}`);\n\n // Disconnect child host if still connected\n if (session.childHostSocketId) {\n const childSocket = io.sockets.sockets.get(session.childHostSocketId);\n if (childSocket) {\n childSocket.disconnect(true);\n }\n }\n\n // Update session state\n session.focus = \"SYSTEM\";\n session.childHostSocketId = undefined;\n session.joinToken = undefined;\n session.activeControllerUrl = undefined;\n session.gameState = \"paused\"; // Reset game state on exit\n\n // Tell all controllers to unload UI\n io.to(roomId).emit(\"client:unloadUi\");\n\n // Tell the system host (arcade) to return to browser view\n if (session.masterHostSocketId) {\n io.to(session.masterHostSocketId).emit(\"server:closeChild\");\n }\n } else if (command === \"toggle_pause\") {\n // Toggle game state\n session.gameState =\n session.gameState === \"playing\" ? \"paused\" : \"playing\";\n\n // Broadcast new state to Room (Host + Controllers)\n const statePayload = {\n roomId,\n state: {\n gameState: session.gameState,\n },\n };\n\n io.to(roomId).emit(\"server:state\", statePayload);\n }\n });\n\n socket.on(\"host:system\", (payload) => {\n const parsed = controllerSystemSchema.safeParse(payload);\n if (!parsed.success) {\n return;\n }\n\n const { roomId, command } = parsed.data;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n return;\n }\n\n if (command === \"toggle_pause\") {\n // Toggle game state - server is source of truth\n session.gameState =\n session.gameState === \"playing\" ? \"paused\" : \"playing\";\n\n // Broadcast new state to Room (Host + Controllers)\n const statePayload = {\n roomId,\n state: {\n gameState: session.gameState,\n },\n };\n\n io.to(roomId).emit(\"server:state\", statePayload);\n }\n });\n\n socket.on(\"host:state\", (payload: ControllerStateMessage) => {\n const result = controllerStateSchema.safeParse(payload);\n if (!result.success) return;\n\n const { roomId, state } = result.data;\n const session = roomManager.getRoom(roomId);\n if (session) {\n // Sync state if provided\n if (state.gameState) {\n session.gameState = state.gameState;\n }\n\n // Broadcast to all controllers\n session.controllers.forEach((c) => {\n io.to(c.socketId).emit(\"server:state\", result.data);\n });\n\n // Broadcast to all hosts (system + child) to keep them in sync\n if (session.masterHostSocketId) {\n io.to(session.masterHostSocketId).emit(\"server:state\", result.data);\n }\n if (session.childHostSocketId) {\n io.to(session.childHostSocketId).emit(\"server:state\", result.data);\n }\n }\n });\n\n socket.on(\"host:signal\", (payload: SignalPayload) => {\n const roomId = roomManager.getRoomByHostId(socket.id);\n if (!roomId) return;\n\n const session = roomManager.getRoom(roomId);\n if (!session) return;\n\n if (payload.targetId) {\n const controller = session.controllers.get(payload.targetId);\n if (controller) {\n io.to(controller.socketId).emit(\"server:signal\", payload);\n }\n } else {\n socket.to(roomId).emit(\"server:signal\", payload);\n }\n });\n\n socket.on(\"host:play_sound\", (payload: PlaySoundEventPayload) => {\n const { roomId, targetControllerId, soundId, volume, loop } = payload;\n const session = roomManager.getRoom(roomId);\n if (!session) return;\n\n const message = { id: soundId, volume, loop };\n\n if (targetControllerId) {\n const controller = session.controllers.get(targetControllerId);\n if (controller) {\n io.to(controller.socketId).emit(\"server:playSound\", message);\n }\n } else {\n socket.to(roomId).emit(\"server:playSound\", message);\n }\n });\n\n socket.on(\"controller:play_sound\", (payload: PlaySoundEventPayload) => {\n const { roomId, soundId, volume, loop } = payload;\n const session = roomManager.getRoom(roomId);\n if (!session) return;\n\n io.to(roomManager.getActiveHostId(session)).emit(\"server:playSound\", {\n id: soundId,\n volume,\n loop,\n });\n });\n\n // --- STORE SYNC (Host -> Server -> All) ---\n socket.on(\"host:state_sync\", (payload: HostStateSyncPayload) => {\n const { roomId, data } = payload;\n const session = roomManager.getRoom(roomId);\n\n // Security: Validate socket.id is a host for this room\n if (!session) {\n return;\n }\n if (\n session.masterHostSocketId !== socket.id &&\n session.childHostSocketId !== socket.id\n ) {\n return;\n }\n\n // Broadcast to room (Controllers + Other Hosts)\n const syncPayload: AirJamStateSyncPayload = {\n roomId,\n data,\n };\n // Use io.to() instead of socket.to() to ensure all sockets in the room receive the broadcast\n io.to(roomId).emit(\"airjam:state_sync\", syncPayload);\n });\n\n // --- ACTION RPC (Controller -> Server -> Host) ---\n socket.on(\n \"controller:action_rpc\",\n (payload: ControllerActionRpcPayload) => {\n const { roomId, actionName, args, controllerId } = payload;\n\n const session = roomManager.getRoom(roomId);\n if (!session) return;\n\n // 1. Verify controller exists in session (by controllerId, not socket.id to handle reconnections)\n const controllerSession = session.controllers.get(controllerId);\n if (!controllerSession) {\n // Controller not found in session - might be a stale connection\n return;\n }\n\n // Update controller's socket ID in case it reconnected\n if (controllerSession.socketId !== socket.id) {\n // Remove old socket mapping\n roomManager.deleteController(controllerSession.socketId);\n // Update to new socket ID\n controllerSession.socketId = socket.id;\n roomManager.setController(socket.id, { roomId, controllerId });\n // Ensure the new socket joins the room\n socket.join(roomId);\n }\n\n // 2. Find the Active Host\n const hostId = roomManager.getActiveHostId(session);\n if (hostId) {\n // 3. Forward to Host (include controllerId so Host knows who sent it)\n const rpcPayload: AirJamActionRpcPayload = {\n actionName,\n args,\n controllerId,\n };\n io.to(hostId).emit(\"airjam:action_rpc\", rpcPayload);\n }\n },\n );\n\n socket.on(\"disconnect\", () => {\n const roomId = roomManager.getRoomByHostId(socket.id);\n if (roomId) {\n const session = roomManager.getRoom(roomId);\n if (!session) {\n roomManager.deleteHost(socket.id);\n return;\n }\n\n if (socket.id === session.childHostSocketId) {\n // Child disconnected\n console.log(`[server] Game host disconnected from room ${roomId}`);\n session.childHostSocketId = undefined;\n session.focus = \"SYSTEM\";\n session.joinToken = undefined;\n session.activeControllerUrl = undefined; // Clear active game URL\n\n // Tell controllers to unload UI\n io.to(roomId).emit(\"client:unloadUi\");\n } else if (socket.id === session.masterHostSocketId) {\n // Master disconnected\n console.log(`[server] Host disconnected from room ${roomId}`);\n\n setTimeout(() => {\n const currentSession = roomManager.getRoom(roomId);\n if (\n currentSession &&\n currentSession.masterHostSocketId === socket.id\n ) {\n console.log(`[server] Removing room ${roomId}`);\n roomManager.removeRoom(roomId, io, \"Host disconnected\");\n }\n }, 3000);\n }\n\n roomManager.deleteHost(socket.id);\n return;\n }\n\n const controller = roomManager.getControllerInfo(socket.id);\n if (controller) {\n const session = roomManager.getRoom(controller.roomId);\n if (session) {\n session.controllers.delete(controller.controllerId);\n const notice: ControllerLeftNotice = {\n controllerId: controller.controllerId,\n };\n io.to(roomManager.getActiveHostId(session)).emit(\n \"server:controllerLeft\",\n notice,\n );\n }\n roomManager.deleteController(socket.id);\n }\n });\n },\n);\n\nhttpServer.listen(PORT, () => {\n console.log(`[air-jam] server listening on http://localhost:${PORT}`);\n});\n","import { and, eq } from \"drizzle-orm\";\nimport { apiKeys, db } from \"../db.js\";\n\n/**\n * API key verification result\n */\nexport interface VerificationResult {\n isVerified: boolean;\n error?: string;\n}\n\n/**\n * Authentication service\n * Handles API key verification\n * In dev mode (no master key, no database), allows all connections\n */\nexport class AuthService {\n private masterKey: string | undefined;\n private databaseUrl: string | undefined;\n private isDevMode: boolean;\n\n constructor() {\n this.masterKey = process.env.AIR_JAM_MASTER_KEY;\n this.databaseUrl = process.env.DATABASE_URL;\n this.isDevMode = !this.masterKey && !this.databaseUrl;\n\n if (this.isDevMode) {\n console.log(\n \"[server] Running in development mode - authentication disabled\",\n );\n } else if (this.masterKey && !this.databaseUrl) {\n console.log(\n \"[server] Running with master key authentication (no database required)\",\n );\n } else if (this.databaseUrl) {\n console.log(\"[server] Running with database authentication\");\n }\n }\n\n /**\n * Verify an API key\n * Returns verification result with optional error message\n * In dev mode, always returns success\n */\n async verifyApiKey(apiKey?: string): Promise<VerificationResult> {\n // Dev mode: no auth required\n if (this.isDevMode) {\n return { isVerified: true };\n }\n\n if (!apiKey) {\n return {\n isVerified: false,\n error: \"Unauthorized: Invalid or Missing API Key\",\n };\n }\n\n // Check master key first\n if (this.masterKey && apiKey === this.masterKey) {\n return { isVerified: true };\n }\n\n // Check database (only if database URL is configured)\n if (!this.databaseUrl || !db) {\n return {\n isVerified: false,\n error: \"Unauthorized: Invalid or Missing API Key\",\n };\n }\n\n try {\n const [keyRecord] = await db\n .select()\n .from(apiKeys)\n .where(and(eq(apiKeys.key, apiKey), eq(apiKeys.isActive, true)))\n .limit(1);\n\n if (keyRecord) {\n // Update last used timestamp (fire and forget)\n db.update(apiKeys)\n .set({ lastUsedAt: new Date() })\n .where(eq(apiKeys.id, keyRecord.id))\n .catch((err: unknown) =>\n console.error(\"[server] Failed to update lastUsedAt\", err),\n );\n\n return { isVerified: true };\n }\n\n return {\n isVerified: false,\n error: \"Unauthorized: Invalid or Missing API Key\",\n };\n } catch (error) {\n console.error(\"[server] Database error during key verification\", error);\n return {\n isVerified: false,\n error: \"Internal Server Error\",\n };\n }\n }\n}\n\n/**\n * Singleton instance\n */\nexport const authService = new AuthService();\n","import * as dotenv from \"dotenv\";\nimport { boolean, pgTable, text, timestamp } from \"drizzle-orm/pg-core\";\nimport { drizzle } from \"drizzle-orm/postgres-js\";\nimport postgres from \"postgres\";\n\ndotenv.config();\n\n// Define only the schema we need for verification\nexport const apiKeys = pgTable(\"api_keys\", {\n id: text(\"id\").primaryKey(),\n gameId: text(\"game_id\").notNull().unique(), // One API key per game\n key: text(\"key\").notNull().unique(),\n isActive: boolean(\"is_active\").default(true).notNull(),\n createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n lastUsedAt: timestamp(\"last_used_at\"),\n});\n\nconst connectionString = process.env.DATABASE_URL;\n\n// Only create database client if DATABASE_URL is provided\n// In dev mode (no DATABASE_URL), the server runs without database\nconst client = connectionString ? postgres(connectionString) : null;\nexport const db = client ? drizzle(client) : null;\n","import type { RoomCode } from \"@air-jam/sdk/protocol\";\nimport type { Server } from \"socket.io\";\nimport type { ControllerIndexEntry, RoomSession } from \"../types.js\";\n\n/**\n * Room manager service\n * Handles all room state management and lookup operations\n */\nexport class RoomManager {\n private rooms = new Map<RoomCode, RoomSession>();\n private hostIndex = new Map<string, RoomCode>();\n private controllerIndex = new Map<string, ControllerIndexEntry>();\n\n /**\n * Get a room by ID\n */\n getRoom(roomId: RoomCode): RoomSession | undefined {\n return this.rooms.get(roomId);\n }\n\n /**\n * Create or update a room\n */\n setRoom(roomId: RoomCode, session: RoomSession): void {\n this.rooms.set(roomId, session);\n }\n\n /**\n * Delete a room\n */\n deleteRoom(roomId: RoomCode): void {\n this.rooms.delete(roomId);\n }\n\n /**\n * Get room ID by host socket ID\n */\n getRoomByHostId(socketId: string): RoomCode | undefined {\n return this.hostIndex.get(socketId);\n }\n\n /**\n * Associate a host socket with a room\n */\n setHostRoom(socketId: string, roomId: RoomCode): void {\n this.hostIndex.set(socketId, roomId);\n }\n\n /**\n * Remove host association\n */\n deleteHost(socketId: string): void {\n this.hostIndex.delete(socketId);\n }\n\n /**\n * Get controller info by socket ID\n */\n getControllerInfo(socketId: string): ControllerIndexEntry | undefined {\n return this.controllerIndex.get(socketId);\n }\n\n /**\n * Associate a controller socket with room and controller ID\n */\n setController(socketId: string, entry: ControllerIndexEntry): void {\n this.controllerIndex.set(socketId, entry);\n }\n\n /**\n * Remove controller association\n */\n deleteController(socketId: string): void {\n this.controllerIndex.delete(socketId);\n }\n\n /**\n * Get the active host socket ID based on focus\n */\n getActiveHostId(session: RoomSession): string {\n return session.focus === \"GAME\" && session.childHostSocketId\n ? session.childHostSocketId\n : session.masterHostSocketId;\n }\n\n /**\n * Remove a room and clean up all associations\n */\n removeRoom(roomId: RoomCode, io: Server, reason: string): void {\n const session = this.rooms.get(roomId);\n if (!session) return;\n\n // Notify all clients\n io.to(roomId).emit(\"server:hostLeft\", { roomId, reason });\n\n // Clean up controller indices\n session.controllers.forEach((controller) => {\n this.controllerIndex.delete(controller.socketId);\n });\n\n // Clean up host indices\n this.hostIndex.delete(session.masterHostSocketId);\n if (session.childHostSocketId) {\n this.hostIndex.delete(session.childHostSocketId);\n }\n\n // Remove room\n this.rooms.delete(roomId);\n }\n\n /**\n * Get all rooms (for debugging/monitoring)\n */\n getAllRooms(): Map<RoomCode, RoomSession> {\n return this.rooms;\n }\n}\n\n/**\n * Singleton instance\n */\nexport const roomManager = new RoomManager();\n"],"mappings":"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","names":["io"]}
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-NWAVO5AJ.js";
2
+ import "./chunk-KMAEESOE.js";
3
3
 
4
4
  // src/cli.ts
5
5
  import dotenv from "dotenv";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import "./chunk-NWAVO5AJ.js";
1
+ import "./chunk-KMAEESOE.js";
2
2
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@air-jam/server",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Air Jam game server for local development and production",
5
5
  "private": false,
6
6
  "type": "module",
@@ -21,8 +21,15 @@
21
21
  "files": [
22
22
  "dist"
23
23
  ],
24
+ "scripts": {
25
+ "dev": "tsx src/index.ts",
26
+ "start": "node dist/cli.js",
27
+ "build": "tsup",
28
+ "typecheck": "tsc --noEmit",
29
+ "prepublishOnly": "pnpm build"
30
+ },
24
31
  "dependencies": {
25
- "@air-jam/sdk": "^0.1.0",
32
+ "@air-jam/sdk": "workspace:^",
26
33
  "color": "^5.0.3",
27
34
  "cors": "^2.8.5",
28
35
  "dotenv": "^17.2.3",
@@ -41,11 +48,5 @@
41
48
  "tsup": "^8.5.1",
42
49
  "tsx": "^4.19.2",
43
50
  "typescript": "~5.9.3"
44
- },
45
- "scripts": {
46
- "dev": "tsx src/index.ts",
47
- "start": "node dist/cli.js",
48
- "build": "tsup",
49
- "typecheck": "tsc --noEmit"
50
51
  }
51
- }
52
+ }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/index.ts","../src/services/auth-service.ts","../src/db.ts","../src/services/room-manager.ts"],"sourcesContent":["import {\n controllerInputSchema,\n controllerJoinSchema,\n controllerLeaveSchema,\n controllerStateSchema,\n controllerSystemSchema,\n ErrorCode,\n hostJoinAsChildSchema,\n hostRegisterSystemSchema,\n hostRegistrationSchema,\n PlaySoundEventPayload,\n SignalPayload,\n systemLaunchGameSchema,\n type ClientToServerEvents,\n type ControllerInputEvent,\n type ControllerJoinedNotice,\n type ControllerJoinPayload,\n type ControllerLeavePayload,\n type ControllerLeftNotice,\n type ControllerStateMessage,\n type HostJoinAsChildPayload,\n type HostRegisterSystemPayload,\n type HostRegistrationPayload,\n type InterServerEvents,\n type PlayerProfile,\n type ServerErrorPayload,\n type ServerToClientEvents,\n type SocketData,\n type SystemLaunchGamePayload,\n} from \"@air-jam/sdk/protocol\";\nimport Color from \"color\";\nimport cors from \"cors\";\nimport express from \"express\";\nimport { createServer } from \"node:http\";\nimport { Server, type Socket } from \"socket.io\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { authService } from \"./services/auth-service.js\";\nimport { roomManager } from \"./services/room-manager.js\";\nimport type { ControllerSession } from \"./types.js\";\n\nconst PORT = Number(process.env.PORT ?? 4000);\n\nconst app = express();\napp.use(cors());\napp.use(express.json());\n\napp.get(\"/health\", (_, res) => {\n res.json({ ok: true });\n});\n\nconst httpServer = createServer(app);\n\nconst io = new Server<\n ClientToServerEvents,\n ServerToClientEvents,\n InterServerEvents,\n SocketData\n>(httpServer, {\n cors: {\n origin: \"*\",\n },\n pingInterval: 2000,\n pingTimeout: 5000,\n});\n\nconst emitError = (socketId: string, payload: ServerErrorPayload): void => {\n io.to(socketId).emit(\"server:error\", payload);\n};\n\nio.on(\n \"connection\",\n (\n socket: Socket<\n ClientToServerEvents,\n ServerToClientEvents,\n InterServerEvents,\n SocketData\n >,\n ) => {\n // --- SYSTEM HOST REGISTRATION (Arcade) ---\n socket.on(\n \"host:registerSystem\",\n async (payload: HostRegisterSystemPayload, callback) => {\n const parsed = hostRegisterSystemSchema.safeParse(payload);\n if (!parsed.success) {\n callback({\n ok: false,\n message: parsed.error.message,\n code: ErrorCode.INVALID_PAYLOAD,\n });\n return;\n }\n\n const { roomId, apiKey } = parsed.data;\n\n // API Key Validation using authService\n const verification = await authService.verifyApiKey(apiKey);\n if (!verification.isVerified) {\n console.warn(\n `[server] Unauthorized host registration attempt for room ${roomId}`,\n );\n callback({\n ok: false,\n message: verification.error,\n code: ErrorCode.INVALID_API_KEY,\n });\n return;\n }\n\n let session = roomManager.getRoom(roomId);\n\n if (session) {\n // Reconnect logic for System Host\n session.masterHostSocketId = socket.id;\n roomManager.setRoom(roomId, session);\n } else {\n // Create new room\n console.log(`[server] Creating room ${roomId}`);\n session = {\n roomId,\n masterHostSocketId: socket.id,\n focus: \"SYSTEM\",\n controllers: new Map(),\n maxPlayers: 32, // Default increased to 32 to allow for observers/queue\n gameState: \"paused\",\n };\n roomManager.setRoom(roomId, session);\n }\n\n roomManager.setHostRoom(socket.id, roomId);\n socket.join(roomId);\n\n callback({ ok: true, roomId });\n io.to(roomId).emit(\"server:roomReady\", { roomId });\n },\n );\n\n // --- LAUNCH GAME (System -> Server) ---\n socket.on(\n \"system:launchGame\",\n (payload: SystemLaunchGamePayload, callback) => {\n const parsed = systemLaunchGameSchema.safeParse(payload);\n if (!parsed.success) {\n callback({\n ok: false,\n message: parsed.error.message,\n code: ErrorCode.INVALID_PAYLOAD,\n });\n return;\n }\n\n const { roomId, gameUrl } = parsed.data;\n const session = roomManager.getRoom(roomId);\n\n if (!session) {\n callback({\n ok: false,\n message: \"Room not found\",\n code: ErrorCode.ROOM_NOT_FOUND,\n });\n return;\n }\n\n if (session.masterHostSocketId !== socket.id) {\n callback({\n ok: false,\n message: \"Unauthorized: Not System Host\",\n code: ErrorCode.UNAUTHORIZED,\n });\n return;\n }\n\n // Check if game is already active\n if (session.childHostSocketId) {\n callback({\n ok: false,\n message: \"Game already active\",\n code: ErrorCode.ALREADY_CONNECTED,\n });\n return;\n }\n\n // Check if a launch is already in progress (joinToken exists but child hasn't joined yet)\n if (session.joinToken && !session.childHostSocketId) {\n callback({ ok: true, joinToken: session.joinToken });\n return;\n }\n\n // Generate Join Token\n const joinToken = uuidv4();\n session.joinToken = joinToken;\n session.activeControllerUrl = gameUrl;\n\n console.log(`[server] Launching game in room ${roomId}`);\n\n // Broadcast to controllers to load UI\n io.to(roomId).emit(\"client:loadUi\", { url: gameUrl });\n\n callback({ ok: true, joinToken });\n },\n );\n\n // --- JOIN AS CHILD (Game -> Server) ---\n socket.on(\n \"host:joinAsChild\",\n (payload: HostJoinAsChildPayload, callback) => {\n const parsed = hostJoinAsChildSchema.safeParse(payload);\n if (!parsed.success) {\n callback({\n ok: false,\n message: parsed.error.message,\n code: ErrorCode.INVALID_PAYLOAD,\n });\n return;\n }\n\n const { roomId, joinToken } = parsed.data;\n const session = roomManager.getRoom(roomId);\n\n if (!session) {\n callback({\n ok: false,\n message: \"Room not found\",\n code: ErrorCode.ROOM_NOT_FOUND,\n });\n return;\n }\n\n if (session.joinToken !== joinToken) {\n console.warn(\n `[server] Invalid join token for room ${roomId}. Expected ${session.joinToken}, got ${joinToken}`,\n );\n callback({\n ok: false,\n message: \"Invalid Join Token\",\n code: ErrorCode.INVALID_TOKEN,\n });\n return;\n }\n\n console.log(`[server] Game host joined room ${roomId}`);\n session.childHostSocketId = socket.id;\n session.focus = \"GAME\"; // Auto-focus on join\n\n roomManager.setHostRoom(socket.id, roomId);\n socket.join(roomId);\n\n // Send initial state to the game\n\n // Small delay to ensure client is ready to receive events after ack\n setTimeout(() => {\n session.controllers.forEach((c) => {\n const notice: ControllerJoinedNotice = {\n controllerId: c.controllerId,\n nickname: c.nickname,\n player: c.playerProfile,\n };\n socket.emit(\"server:controllerJoined\", notice);\n });\n\n // Send current game state to the child host\n const statePayload = {\n roomId,\n state: {\n gameState: session.gameState,\n },\n };\n socket.emit(\"server:state\", statePayload);\n }, 100);\n\n callback({ ok: true, roomId });\n },\n );\n\n // --- CLOSE GAME (System -> Server) ---\n socket.on(\"system:closeGame\", (payload: { roomId: string }) => {\n const { roomId } = payload;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n return;\n }\n\n if (session.masterHostSocketId !== socket.id) {\n return;\n }\n\n console.log(`[server] Closing game in room ${roomId}`);\n\n // Disconnect child host if still connected\n if (session.childHostSocketId) {\n const childSocket = io.sockets.sockets.get(session.childHostSocketId);\n if (childSocket) {\n childSocket.disconnect(true);\n }\n }\n\n session.focus = \"SYSTEM\";\n session.childHostSocketId = undefined;\n session.joinToken = undefined;\n session.activeControllerUrl = undefined;\n\n // Tell controllers to unload UI\n io.to(roomId).emit(\"client:unloadUi\");\n });\n\n // --- LEGACY/STANDALONE HOST REGISTER ---\n // Keeping this for standalone development where there is no \"System\"\n socket.on(\n \"host:register\",\n async (payload: HostRegistrationPayload, callback) => {\n const parsed = hostRegistrationSchema.safeParse(payload);\n if (!parsed.success) {\n callback({\n ok: false,\n message: parsed.error.message,\n code: ErrorCode.INVALID_PAYLOAD,\n });\n return;\n }\n const { roomId, maxPlayers } = parsed.data;\n\n // If mode is 'child', we should redirect them to use host:join_as_child if possible,\n // but for standalone dev, they might use this.\n // For now, we treat 'host:register' as creating a STANDALONE room or joining as master.\n\n let session = roomManager.getRoom(roomId);\n if (session) {\n // If room exists, we assume they are taking over or reconnecting as Master\n session.masterHostSocketId = socket.id;\n session.focus = \"SYSTEM\"; // Default to system/master focus\n } else {\n console.log(`[server] Creating standalone room ${roomId}`);\n session = {\n roomId,\n masterHostSocketId: socket.id,\n focus: \"SYSTEM\",\n controllers: new Map(),\n maxPlayers,\n gameState: \"paused\",\n };\n roomManager.setRoom(roomId, session);\n }\n\n roomManager.setHostRoom(socket.id, roomId);\n socket.join(roomId);\n callback({ ok: true, roomId });\n io.to(roomId).emit(\"server:roomReady\", { roomId });\n },\n );\n\n socket.on(\"controller:join\", (payload: ControllerJoinPayload, callback) => {\n const parsed = controllerJoinSchema.safeParse(payload);\n if (!parsed.success) {\n callback({\n ok: false,\n message: parsed.error.message,\n code: ErrorCode.INVALID_PAYLOAD,\n });\n return;\n }\n const { roomId, controllerId, nickname } = parsed.data;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n callback({\n ok: false,\n message: \"Room not found\",\n code: ErrorCode.ROOM_NOT_FOUND,\n });\n emitError(socket.id, {\n code: ErrorCode.ROOM_NOT_FOUND,\n message: \"Room not found\",\n });\n return;\n }\n\n // When a controller joins, we usually check maxPlayers.\n // However, for the ARCADE room, we want to allow MORE players than the game might support,\n // so they can queue up or watch.\n // But the current logic enforces `session.maxPlayers`.\n // If we want to allow \"observers\" or \"queue\", we should increase maxPlayers for the Arcade room itself.\n // The GAME itself (Child Host) might enforce its own player limit logic by ignoring inputs from extra players.\n\n // For now, let's keep the hard limit on the Room but maybe bump the default.\n if (session.controllers.size >= session.maxPlayers) {\n callback({\n ok: false,\n message: \"Room full\",\n code: ErrorCode.ROOM_FULL,\n });\n emitError(socket.id, {\n code: ErrorCode.ROOM_FULL,\n message: \"Room is full\",\n });\n return;\n }\n\n const existing = session.controllers.get(controllerId);\n if (existing) {\n roomManager.deleteController(existing.socketId);\n }\n\n const PLAYER_COLORS = [\n \"#38bdf8\",\n \"#a78bfa\",\n \"#f472b6\",\n \"#34d399\",\n \"#fbbf24\",\n \"#60a5fa\",\n \"#c084fc\",\n \"#fb7185\",\n \"#4ade80\",\n \"#f87171\",\n \"#22d3ee\",\n \"#a855f7\",\n \"#ec4899\",\n \"#10b981\",\n \"#f59e0b\",\n \"#3b82f6\",\n \"#8b5cf6\",\n \"#ef4444\",\n \"#14b8a6\",\n \"#f97316\",\n ];\n const colorHex =\n PLAYER_COLORS[session.controllers.size % PLAYER_COLORS.length];\n\n let color: string;\n try {\n color = Color(colorHex).hex();\n } catch {\n color = Color(\"#38bdf8\").hex();\n }\n\n const playerProfile: PlayerProfile = {\n id: controllerId,\n label: nickname ?? `Player ${session.controllers.size}`,\n color,\n };\n\n const controllerSession: ControllerSession = {\n controllerId,\n nickname,\n socketId: socket.id,\n playerProfile,\n };\n\n session.controllers.set(controllerId, controllerSession);\n roomManager.setController(socket.id, { roomId, controllerId });\n socket.join(roomId);\n\n const notice: ControllerJoinedNotice = {\n controllerId,\n nickname,\n player: playerProfile,\n };\n\n // Emit to Active Host based on Focus\n io.to(roomManager.getActiveHostId(session)).emit(\n \"server:controllerJoined\",\n notice,\n );\n\n callback({ ok: true, controllerId, roomId });\n\n const welcomePayload = {\n controllerId,\n roomId,\n player: playerProfile,\n };\n\n socket.emit(\"server:welcome\", welcomePayload);\n\n // Send current game state to the new controller\n const statePayload = {\n roomId,\n state: {\n gameState: session.gameState,\n },\n };\n socket.emit(\"server:state\", statePayload);\n\n // IMPORTANT: If a game is already active (activeControllerUrl set),\n // we must tell the new controller to load the game UI immediately.\n // We check activeControllerUrl instead of childHostSocketId because the game might be\n // in the process of loading (launched but not yet connected) or momentarily disconnected.\n if (session.activeControllerUrl) {\n socket.emit(\"client:loadUi\", { url: session.activeControllerUrl });\n }\n\n console.log(\n `[server] Controller joined room ${roomId} (${session.controllers.size}/${session.maxPlayers} players)`,\n );\n });\n\n socket.on(\"controller:leave\", (payload: ControllerLeavePayload) => {\n const parsed = controllerLeaveSchema.safeParse(payload);\n if (!parsed.success) {\n return;\n }\n const { roomId, controllerId } = parsed.data;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n return;\n }\n session.controllers.delete(controllerId);\n roomManager.deleteController(socket.id);\n const notice: ControllerLeftNotice = { controllerId };\n io.to(roomManager.getActiveHostId(session)).emit(\n \"server:controllerLeft\",\n notice,\n );\n socket.leave(roomId);\n });\n\n socket.on(\"controller:input\", (payload: ControllerInputEvent) => {\n // Validate roomId and controllerId, but accept arbitrary input structure\n const result = controllerInputSchema.safeParse(payload);\n if (!result.success) {\n return;\n }\n\n const { roomId } = result.data;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n return;\n }\n\n // Route based on FOCUS - pass through arbitrary input to host\n const targetHostId = roomManager.getActiveHostId(session);\n if (targetHostId) {\n io.to(targetHostId).emit(\"server:input\", result.data);\n }\n });\n\n socket.on(\"controller:system\", (payload) => {\n const parsed = controllerSystemSchema.safeParse(payload);\n if (!parsed.success) {\n return;\n }\n\n const { roomId, command } = parsed.data;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n return;\n }\n\n if (command === \"exit\") {\n // Controller wants to exit the game - close the game on the server\n console.log(`[server] Controller exit request in room ${roomId}`);\n\n // Disconnect child host if still connected\n if (session.childHostSocketId) {\n const childSocket = io.sockets.sockets.get(session.childHostSocketId);\n if (childSocket) {\n childSocket.disconnect(true);\n }\n }\n\n // Update session state\n session.focus = \"SYSTEM\";\n session.childHostSocketId = undefined;\n session.joinToken = undefined;\n session.activeControllerUrl = undefined;\n session.gameState = \"paused\"; // Reset game state on exit\n\n // Tell all controllers to unload UI\n io.to(roomId).emit(\"client:unloadUi\");\n\n // Tell the system host (arcade) to return to browser view\n if (session.masterHostSocketId) {\n io.to(session.masterHostSocketId).emit(\"server:closeChild\");\n }\n } else if (command === \"toggle_pause\") {\n // Toggle game state\n session.gameState =\n session.gameState === \"playing\" ? \"paused\" : \"playing\";\n\n // Broadcast new state to Room (Host + Controllers)\n const statePayload = {\n roomId,\n state: {\n gameState: session.gameState,\n },\n };\n\n io.to(roomId).emit(\"server:state\", statePayload);\n }\n });\n\n socket.on(\"host:system\", (payload) => {\n const parsed = controllerSystemSchema.safeParse(payload);\n if (!parsed.success) {\n return;\n }\n\n const { roomId, command } = parsed.data;\n const session = roomManager.getRoom(roomId);\n if (!session) {\n return;\n }\n\n if (command === \"toggle_pause\") {\n // Toggle game state - server is source of truth\n session.gameState =\n session.gameState === \"playing\" ? \"paused\" : \"playing\";\n\n // Broadcast new state to Room (Host + Controllers)\n const statePayload = {\n roomId,\n state: {\n gameState: session.gameState,\n },\n };\n\n io.to(roomId).emit(\"server:state\", statePayload);\n }\n });\n\n socket.on(\"host:state\", (payload: ControllerStateMessage) => {\n const result = controllerStateSchema.safeParse(payload);\n if (!result.success) return;\n\n const { roomId, state } = result.data;\n const session = roomManager.getRoom(roomId);\n if (session) {\n // Sync state if provided\n if (state.gameState) {\n session.gameState = state.gameState;\n }\n\n // Broadcast to all controllers\n session.controllers.forEach((c) => {\n io.to(c.socketId).emit(\"server:state\", result.data);\n });\n\n // Broadcast to all hosts (system + child) to keep them in sync\n if (session.masterHostSocketId) {\n io.to(session.masterHostSocketId).emit(\"server:state\", result.data);\n }\n if (session.childHostSocketId) {\n io.to(session.childHostSocketId).emit(\"server:state\", result.data);\n }\n }\n });\n\n socket.on(\"host:signal\", (payload: SignalPayload) => {\n const roomId = roomManager.getRoomByHostId(socket.id);\n if (!roomId) return;\n\n const session = roomManager.getRoom(roomId);\n if (!session) return;\n\n if (payload.targetId) {\n const controller = session.controllers.get(payload.targetId);\n if (controller) {\n io.to(controller.socketId).emit(\"server:signal\", payload);\n }\n } else {\n socket.to(roomId).emit(\"server:signal\", payload);\n }\n });\n\n socket.on(\"host:play_sound\", (payload: PlaySoundEventPayload) => {\n const { roomId, targetControllerId, soundId, volume, loop } = payload;\n const session = roomManager.getRoom(roomId);\n if (!session) return;\n\n const message = { id: soundId, volume, loop };\n\n if (targetControllerId) {\n const controller = session.controllers.get(targetControllerId);\n if (controller) {\n io.to(controller.socketId).emit(\"server:playSound\", message);\n }\n } else {\n socket.to(roomId).emit(\"server:playSound\", message);\n }\n });\n\n socket.on(\"controller:play_sound\", (payload: PlaySoundEventPayload) => {\n const { roomId, soundId, volume, loop } = payload;\n const session = roomManager.getRoom(roomId);\n if (!session) return;\n\n io.to(roomManager.getActiveHostId(session)).emit(\"server:playSound\", {\n id: soundId,\n volume,\n loop,\n });\n });\n\n socket.on(\"disconnect\", () => {\n const roomId = roomManager.getRoomByHostId(socket.id);\n if (roomId) {\n const session = roomManager.getRoom(roomId);\n if (!session) {\n roomManager.deleteHost(socket.id);\n return;\n }\n\n if (socket.id === session.childHostSocketId) {\n // Child disconnected\n console.log(`[server] Game host disconnected from room ${roomId}`);\n session.childHostSocketId = undefined;\n session.focus = \"SYSTEM\";\n session.joinToken = undefined;\n session.activeControllerUrl = undefined; // Clear active game URL\n\n // Tell controllers to unload UI\n io.to(roomId).emit(\"client:unloadUi\");\n } else if (socket.id === session.masterHostSocketId) {\n // Master disconnected\n console.log(`[server] Host disconnected from room ${roomId}`);\n\n setTimeout(() => {\n const currentSession = roomManager.getRoom(roomId);\n if (\n currentSession &&\n currentSession.masterHostSocketId === socket.id\n ) {\n console.log(`[server] Removing room ${roomId}`);\n roomManager.removeRoom(roomId, io, \"Host disconnected\");\n }\n }, 3000);\n }\n\n roomManager.deleteHost(socket.id);\n return;\n }\n\n const controller = roomManager.getControllerInfo(socket.id);\n if (controller) {\n const session = roomManager.getRoom(controller.roomId);\n if (session) {\n session.controllers.delete(controller.controllerId);\n const notice: ControllerLeftNotice = {\n controllerId: controller.controllerId,\n };\n io.to(roomManager.getActiveHostId(session)).emit(\n \"server:controllerLeft\",\n notice,\n );\n }\n roomManager.deleteController(socket.id);\n }\n });\n },\n);\n\nhttpServer.listen(PORT, () => {\n console.log(`[air-jam] server listening on http://localhost:${PORT}`);\n});\n","import { and, eq } from \"drizzle-orm\";\nimport { apiKeys, db } from \"../db.js\";\n\n/**\n * API key verification result\n */\nexport interface VerificationResult {\n isVerified: boolean;\n error?: string;\n}\n\n/**\n * Authentication service\n * Handles API key verification\n * In dev mode (no master key, no database), allows all connections\n */\nexport class AuthService {\n private masterKey: string | undefined;\n private databaseUrl: string | undefined;\n private isDevMode: boolean;\n\n constructor() {\n this.masterKey = process.env.AIR_JAM_MASTER_KEY;\n this.databaseUrl = process.env.DATABASE_URL;\n this.isDevMode = !this.masterKey && !this.databaseUrl;\n\n if (this.isDevMode) {\n console.log(\n \"[server] Running in development mode - authentication disabled\",\n );\n } else if (this.masterKey && !this.databaseUrl) {\n console.log(\n \"[server] Running with master key authentication (no database required)\",\n );\n } else if (this.databaseUrl) {\n console.log(\"[server] Running with database authentication\");\n }\n }\n\n /**\n * Verify an API key\n * Returns verification result with optional error message\n * In dev mode, always returns success\n */\n async verifyApiKey(apiKey?: string): Promise<VerificationResult> {\n // Dev mode: no auth required\n if (this.isDevMode) {\n return { isVerified: true };\n }\n\n if (!apiKey) {\n return {\n isVerified: false,\n error: \"Unauthorized: Invalid or Missing API Key\",\n };\n }\n\n // Check master key first\n if (this.masterKey && apiKey === this.masterKey) {\n return { isVerified: true };\n }\n\n // Check database (only if database URL is configured)\n if (!this.databaseUrl || !db) {\n return {\n isVerified: false,\n error: \"Unauthorized: Invalid or Missing API Key\",\n };\n }\n\n try {\n const [keyRecord] = await db\n .select()\n .from(apiKeys)\n .where(and(eq(apiKeys.key, apiKey), eq(apiKeys.isActive, true)))\n .limit(1);\n\n if (keyRecord) {\n // Update last used timestamp (fire and forget)\n db.update(apiKeys)\n .set({ lastUsedAt: new Date() })\n .where(eq(apiKeys.id, keyRecord.id))\n .catch((err: unknown) =>\n console.error(\"[server] Failed to update lastUsedAt\", err),\n );\n\n return { isVerified: true };\n }\n\n return {\n isVerified: false,\n error: \"Unauthorized: Invalid or Missing API Key\",\n };\n } catch (error) {\n console.error(\"[server] Database error during key verification\", error);\n return {\n isVerified: false,\n error: \"Internal Server Error\",\n };\n }\n }\n}\n\n/**\n * Singleton instance\n */\nexport const authService = new AuthService();\n","import * as dotenv from \"dotenv\";\nimport { boolean, pgTable, text, timestamp } from \"drizzle-orm/pg-core\";\nimport { drizzle } from \"drizzle-orm/postgres-js\";\nimport postgres from \"postgres\";\n\ndotenv.config();\n\n// Define only the schema we need for verification\nexport const apiKeys = pgTable(\"api_keys\", {\n id: text(\"id\").primaryKey(),\n gameId: text(\"game_id\").notNull().unique(), // One API key per game\n key: text(\"key\").notNull().unique(),\n isActive: boolean(\"is_active\").default(true).notNull(),\n createdAt: timestamp(\"created_at\").defaultNow().notNull(),\n lastUsedAt: timestamp(\"last_used_at\"),\n});\n\nconst connectionString = process.env.DATABASE_URL;\n\n// Only create database client if DATABASE_URL is provided\n// In dev mode (no DATABASE_URL), the server runs without database\nconst client = connectionString ? postgres(connectionString) : null;\nexport const db = client ? drizzle(client) : null;\n","import type { RoomCode } from \"@air-jam/sdk/protocol\";\nimport type { Server } from \"socket.io\";\nimport type { ControllerIndexEntry, RoomSession } from \"../types.js\";\n\n/**\n * Room manager service\n * Handles all room state management and lookup operations\n */\nexport class RoomManager {\n private rooms = new Map<RoomCode, RoomSession>();\n private hostIndex = new Map<string, RoomCode>();\n private controllerIndex = new Map<string, ControllerIndexEntry>();\n\n /**\n * Get a room by ID\n */\n getRoom(roomId: RoomCode): RoomSession | undefined {\n return this.rooms.get(roomId);\n }\n\n /**\n * Create or update a room\n */\n setRoom(roomId: RoomCode, session: RoomSession): void {\n this.rooms.set(roomId, session);\n }\n\n /**\n * Delete a room\n */\n deleteRoom(roomId: RoomCode): void {\n this.rooms.delete(roomId);\n }\n\n /**\n * Get room ID by host socket ID\n */\n getRoomByHostId(socketId: string): RoomCode | undefined {\n return this.hostIndex.get(socketId);\n }\n\n /**\n * Associate a host socket with a room\n */\n setHostRoom(socketId: string, roomId: RoomCode): void {\n this.hostIndex.set(socketId, roomId);\n }\n\n /**\n * Remove host association\n */\n deleteHost(socketId: string): void {\n this.hostIndex.delete(socketId);\n }\n\n /**\n * Get controller info by socket ID\n */\n getControllerInfo(socketId: string): ControllerIndexEntry | undefined {\n return this.controllerIndex.get(socketId);\n }\n\n /**\n * Associate a controller socket with room and controller ID\n */\n setController(socketId: string, entry: ControllerIndexEntry): void {\n this.controllerIndex.set(socketId, entry);\n }\n\n /**\n * Remove controller association\n */\n deleteController(socketId: string): void {\n this.controllerIndex.delete(socketId);\n }\n\n /**\n * Get the active host socket ID based on focus\n */\n getActiveHostId(session: RoomSession): string {\n return session.focus === \"GAME\" && session.childHostSocketId\n ? session.childHostSocketId\n : session.masterHostSocketId;\n }\n\n /**\n * Remove a room and clean up all associations\n */\n removeRoom(roomId: RoomCode, io: Server, reason: string): void {\n const session = this.rooms.get(roomId);\n if (!session) return;\n\n // Notify all clients\n io.to(roomId).emit(\"server:hostLeft\", { roomId, reason });\n\n // Clean up controller indices\n session.controllers.forEach((controller) => {\n this.controllerIndex.delete(controller.socketId);\n });\n\n // Clean up host indices\n this.hostIndex.delete(session.masterHostSocketId);\n if (session.childHostSocketId) {\n this.hostIndex.delete(session.childHostSocketId);\n }\n\n // Remove room\n this.rooms.delete(roomId);\n }\n\n /**\n * Get all rooms (for debugging/monitoring)\n */\n getAllRooms(): Map<RoomCode, RoomSession> {\n return this.rooms;\n }\n}\n\n/**\n * Singleton instance\n */\nexport const roomManager = new RoomManager();\n"],"mappings":"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","names":["io"]}