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