diff --git a/server/src/index.js b/server/src/index.js index 3d3fe2a..febd97d 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -7,7 +7,7 @@ import http from 'http'; import { Server as IOServer } from 'socket.io'; import jwt from 'jsonwebtoken'; -import { init as dbInit, Users, Characters, Inventory, query } from './db.js'; +import { init as dbInit, Characters, Inventory, query } from './db.js'; const app = express(); app.use(helmet()); @@ -15,15 +15,15 @@ app.use(cors({ origin: true, credentials: true })); app.use(express.json()); app.use(cookieParser()); -// simple world config const WORLD = { width: 4000, height: 3000, gatherRange: 40 }; -// ===== Auth helpers ===== +const COOKIE = 'auth'; +// JWT payload: { cid: characterId } function signToken(cid) { return jwt.sign({ cid }, process.env.JWT_SECRET, { expiresIn: '30d' }); } @@ -32,7 +32,7 @@ function authFromReq(req) { try { const token = req.cookies?.[COOKIE]; if (!token) return null; - return jwt.verify(token, process.env.JWT_SECRET); // { cid, iat, exp } + return jwt.verify(token, process.env.JWT_SECRET); } catch { return null; } @@ -40,7 +40,7 @@ function authFromReq(req) { // ===== HTTP API ===== -// Register a new character with username + password +// Register: username+password per character app.post('/api/register', async (req, res) => { const { username, password } = req.body || {}; if (!username || !password) { @@ -59,7 +59,7 @@ app.post('/api/register', async (req, res) => { } }); -// Login as a specific character +// Login: per-character app.post('/api/login', async (req, res) => { const { username, password } = req.body || {}; if (!username || !password) { @@ -75,12 +75,12 @@ app.post('/api/login', async (req, res) => { .json({ ok: true }); }); -// Logout: clear cookie +// Logout app.post('/api/logout', (req, res) => { res.clearCookie(COOKIE).json({ ok: true }); }); -// Current character + inventory +// Current character app.get('/api/me', async (req, res) => { const auth = authFromReq(req); if (!auth) return res.status(401).json({ error: 'Not logged in' }); @@ -92,8 +92,7 @@ app.get('/api/me', async (req, res) => { res.json({ character: ch, inventory: inv, world: WORLD }); }); - -// static client +// Serve client app.use(express.static(new URL('../static', import.meta.url).pathname)); const server = http.createServer(app); @@ -101,13 +100,14 @@ const io = new IOServer(server, { cors: { origin: true, credentials: true } }); -// ===== In-memory world ===== +// ===== World: resource nodes ===== let nodes = []; + function spawnNodes() { nodes = []; + const types = ['wood', 'stone', 'ore', 'fiber']; for (let i = 0; i < 120; i++) { - const types = ['wood', 'stone', 'ore', 'fiber']; const type = types[i % types.length]; nodes.push({ id: i, @@ -119,19 +119,23 @@ function spawnNodes() { }); } } + spawnNodes(); -// socket player info (very simple) -const socketsToPlayers = new Map(); // socket.id -> { x,y,name,uid,charId } +// socket.id -> player state +const socketsToPlayers = new Map(); // { x,y,name,cid,charId } + +// ===== Socket.IO ===== io.on('connection', socket => { + // character auth from cookie socket.on('auth:join', async ack => { try { const cookieHeader = socket.handshake.headers.cookie || ''; const match = cookieHeader.match(/auth=([^;]+)/); if (!match) return ack?.({ error: 'no auth cookie' }); - const token = match[1]; + const token = decodeURIComponent(match[1]); const decoded = jwt.verify(token, process.env.JWT_SECRET); // { cid } const ch = await Characters.getById(decoded.cid); @@ -148,7 +152,7 @@ io.on('connection', socket => { socket.join('world'); - // Send initial nodes in same handler or from separate listener + // send initial nodes socket.emit( 'nodes:init', nodes.map(n => ({ @@ -171,10 +175,10 @@ io.on('connection', socket => { } }); + // very basic server-side movement (trusting client target) socket.on('move:click', target => { const p = socketsToPlayers.get(socket.id); if (!p) return; - // for now, we just trust and update directly (you can add validation later) p.x = Math.max(0, Math.min(WORLD.width, target.x)); p.y = Math.max(0, Math.min(WORLD.height, target.y)); }); @@ -182,6 +186,7 @@ io.on('connection', socket => { socket.on('gather', async ({ nodeId }, ack) => { const p = socketsToPlayers.get(socket.id); if (!p) return ack?.({ error: 'not authed' }); + const node = nodes.find(n => n.id === nodeId); if (!node || !node.alive) return ack?.({ error: 'invalid node' }); @@ -216,7 +221,7 @@ io.on('connection', socket => { }); }); -// game loop: respawn nodes + broadcast simple state +// game loop: respawn nodes + broadcast positions setInterval(() => { const now = Date.now(); for (const n of nodes) { @@ -226,7 +231,6 @@ setInterval(() => { } } - // player snapshot const players = []; for (const [id, p] of socketsToPlayers) { players.push({ id, name: p.name, x: p.x, y: p.y });