Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3b5c38d0a |
@@ -1,5 +1,14 @@
|
|||||||
|
# .env.example
|
||||||
|
|
||||||
# Port the server listens on (client must connect to this)
|
# Port the server listens on (client must connect to this)
|
||||||
PORT=5055
|
PORT=5055
|
||||||
|
|
||||||
# Logging: error, warn, info, verbose, debug, silly
|
# Logging: error, warn, info, verbose, debug, silly
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
|
|
||||||
|
# Steam/RecNet
|
||||||
|
STEAM_WEB_API_KEY=
|
||||||
|
RECNET_VERIFY_URL=
|
||||||
|
|
||||||
|
# SQLite DB file
|
||||||
|
SQLITE_FILE=./helium.db
|
||||||
|
|||||||
@@ -1 +1,32 @@
|
|||||||
|
# HeliumRecServer (custom-node-server branch)
|
||||||
|
|
||||||
|
This branch adds a Node.js skeleton for running a custom Rec Room (2023) server replacement that you can adapt to work with the Photon-based client by swapping keys/addresses in the client.
|
||||||
|
|
||||||
|
What I added in this commit:
|
||||||
|
- WebSocket server skeleton (src/index.js) that accepts query-token authentication and persists minimal user data
|
||||||
|
- Protocol stub (src/protocol.js) — where Rec Room / Photon packet parsing should be implemented
|
||||||
|
- Auth helpers (src/auth.js) for Steam/RecNet token verification (placeholders that call configured endpoints)
|
||||||
|
- SQLite persistence (src/storage.js)
|
||||||
|
- Logger and test client
|
||||||
|
- package.json with dependencies
|
||||||
|
|
||||||
|
Environment variables (set in .env):
|
||||||
|
- PORT (default 5055) — WebSocket server port
|
||||||
|
- LOG_LEVEL — winston log level (info/debug/...)
|
||||||
|
- STEAM_WEB_API_KEY — Steam Web API key (if you plan to use Steam verification)
|
||||||
|
- RECNET_VERIFY_URL — RecNet verification endpoint to POST { token }
|
||||||
|
- SQLITE_FILE — path for the SQLite DB file (default ./helium.db)
|
||||||
|
|
||||||
|
How to run:
|
||||||
|
1. Copy `.env.example` to `.env` and fill in the variables you need.
|
||||||
|
2. npm install
|
||||||
|
3. npm start
|
||||||
|
|
||||||
|
Notes about Photon & Rec Room:
|
||||||
|
- The client uses Photon; this skeleton provides a WebSocket entry point you can use for reverse engineering and testing. Photon traffic is binary and has its own framing — you'll need to implement Photon protocol handling inside src/protocol.js (or add a UDP/TCP transport if Photon expects that).
|
||||||
|
- You said you can replace Photon keys & localhost yourself — point the client at this process and replace any required AppId/AppVersion in the client.
|
||||||
|
|
||||||
|
Next steps I can take after you confirm:
|
||||||
|
- Implement Photon framing and basic handshake to accept a 2023 Rec Room client (requires packet captures or protocol docs).
|
||||||
|
- Implement Steam/RecNet direct verification flow if you provide endpoints or tokens to test.
|
||||||
|
- Add more persistence (Redis) and matchmaking logic.
|
||||||
|
|||||||
+3
-1
@@ -10,6 +10,8 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ws": "^8.13.0",
|
"ws": "^8.13.0",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"winston": "^3.8.0"
|
"winston": "^3.8.0",
|
||||||
|
"axios": "^1.4.0",
|
||||||
|
"sqlite3": "^5.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+4
-49
@@ -1,50 +1,5 @@
|
|||||||
const express = require('express');
|
// The admin console was intentionally removed from the project per repository owner request.
|
||||||
const basicAuth = require('basic-auth');
|
// If you need to restore it later, re-add an admin HTTP server (e.g., using express) and
|
||||||
const logger = require('./logger');
|
// provide ADMIN_USER / ADMIN_PASS environment variables for basic auth.
|
||||||
|
|
||||||
let serverInstance;
|
module.exports = {};
|
||||||
|
|
||||||
function ensureAuth(req, res, next) {
|
|
||||||
if (!process.env.ADMIN_USER || !process.env.ADMIN_PASS) return next();
|
|
||||||
const user = basicAuth(req);
|
|
||||||
if (!user || user.name !== process.env.ADMIN_USER || user.pass !== process.env.ADMIN_PASS) {
|
|
||||||
res.set('WWW-Authenticate', 'Basic realm="HeliumRecAdmin"');
|
|
||||||
return res.status(401).send('Authentication required');
|
|
||||||
}
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
function start({ port = 5056, getConnections }) {
|
|
||||||
const app = express();
|
|
||||||
app.use(express.json());
|
|
||||||
|
|
||||||
app.get('/health', (req, res) => res.json({ ok: true }));
|
|
||||||
|
|
||||||
app.use(ensureAuth);
|
|
||||||
|
|
||||||
app.get('/connections', (req, res) => {
|
|
||||||
const conns = [];
|
|
||||||
const map = getConnections();
|
|
||||||
map.forEach((v, k) => {
|
|
||||||
conns.push({ id: k, user: v.user, connectedAt: v.connectedAt, remote: v.ws?._meta?.remote });
|
|
||||||
});
|
|
||||||
res.json(conns);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/broadcast', (req, res) => {
|
|
||||||
const { message } = req.body || {};
|
|
||||||
if (!message) return res.status(400).json({ error: 'message required' });
|
|
||||||
const map = getConnections();
|
|
||||||
let sent = 0;
|
|
||||||
map.forEach((v) => {
|
|
||||||
try { v.ws.send(typeof message === 'string' ? message : JSON.stringify(message)); sent++; } catch (e) { logger.warn('broadcast failed', e.message); }
|
|
||||||
});
|
|
||||||
res.json({ sent });
|
|
||||||
});
|
|
||||||
|
|
||||||
serverInstance = app.listen(port, () => logger.info(`Admin console listening on http://0.0.0.0:${port}`));
|
|
||||||
}
|
|
||||||
|
|
||||||
function stop() { if (serverInstance) serverInstance.close(); }
|
|
||||||
|
|
||||||
module.exports = { start, stop };
|
|
||||||
|
|||||||
+15
-6
@@ -8,16 +8,28 @@ const logger = require('./logger');
|
|||||||
const protocol = require('./protocol');
|
const protocol = require('./protocol');
|
||||||
const auth = require('./auth');
|
const auth = require('./auth');
|
||||||
const storage = require('./storage');
|
const storage = require('./storage');
|
||||||
const admin = require('./admin');
|
|
||||||
|
|
||||||
const PORT = process.env.PORT || 5055;
|
const PORT = process.env.PORT || 5055;
|
||||||
const ADMIN_PORT = process.env.ADMIN_PORT || 5056;
|
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
await storage.init();
|
await storage.init();
|
||||||
|
|
||||||
// Start WebSocket server on its own HTTP server so we can inspect req.url
|
// Start WebSocket server on its own HTTP server so we can inspect req.url
|
||||||
const server = http.createServer();
|
const server = http.createServer((req, res) => {
|
||||||
|
// Provide a minimal informational page for HTTP GETs so browsers don't see "Cannot GET /"
|
||||||
|
if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||||
|
res.end(`<html><body>
|
||||||
|
<h1>HeliumRecServer</h1>
|
||||||
|
<p>This port is a WebSocket endpoint. Connect using a WebSocket client.</p>
|
||||||
|
<p>Server listens on ws://0.0.0.0:${PORT}</p>
|
||||||
|
</body></html>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// leave other requests to return 404
|
||||||
|
res.writeHead(404); res.end('Not Found');
|
||||||
|
});
|
||||||
|
|
||||||
const wss = new WebSocket.Server({ noServer: true });
|
const wss = new WebSocket.Server({ noServer: true });
|
||||||
|
|
||||||
const connections = new Map(); // key -> { ws, user }
|
const connections = new Map(); // key -> { ws, user }
|
||||||
@@ -79,7 +91,4 @@ const ADMIN_PORT = process.env.ADMIN_PORT || 5056;
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.listen(PORT, () => logger.info(`HeliumRecServer WebSocket listening on ws://0.0.0.0:${PORT}`));
|
server.listen(PORT, () => logger.info(`HeliumRecServer WebSocket listening on ws://0.0.0.0:${PORT}`));
|
||||||
|
|
||||||
// Start admin console (HTTP) bound to ADMIN_PORT
|
|
||||||
admin.start({ port: ADMIN_PORT, getConnections: () => connections });
|
|
||||||
})();
|
})();
|
||||||
|
|||||||
Reference in New Issue
Block a user