Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e3b5c38d0a |
@@ -2,7 +2,6 @@
|
||||
|
||||
# Port the server listens on (client must connect to this)
|
||||
PORT=5055
|
||||
ADMIN_PORT=5056
|
||||
|
||||
# Logging: error, warn, info, verbose, debug, silly
|
||||
LOG_LEVEL=info
|
||||
@@ -13,7 +12,3 @@ RECNET_VERIFY_URL=
|
||||
|
||||
# SQLite DB file
|
||||
SQLITE_FILE=./helium.db
|
||||
|
||||
# Admin basic auth (optional)
|
||||
ADMIN_USER=admin
|
||||
ADMIN_PASS=changeme
|
||||
|
||||
@@ -7,28 +7,24 @@ What I added in this commit:
|
||||
- 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)
|
||||
- Admin console (src/admin.js) with basic auth and endpoints to list connections and broadcast messages
|
||||
- Logger and test client
|
||||
- package.json with dependencies
|
||||
|
||||
Environment variables (set in .env):
|
||||
- PORT (default 5055) — WebSocket server port
|
||||
- ADMIN_PORT (default 5056) — Admin HTTP 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)
|
||||
- ADMIN_USER / ADMIN_PASS — optional Basic auth for admin console
|
||||
|
||||
How to run:
|
||||
1. Copy `.env.example` to `.env` and fill in the variables you need.
|
||||
2. npm install
|
||||
3. npm start
|
||||
4. Admin console: http://localhost:5056 (use basic auth if ADMIN_USER/ADMIN_PASS set)
|
||||
|
||||
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 and localhost yourself — good. Put the Photon server host/port to point to this process and replace any required AppId/AppVersion in the client.
|
||||
- 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).
|
||||
|
||||
+2
-5
@@ -5,16 +5,13 @@
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js",
|
||||
"admin": "node src/admin.js"
|
||||
"dev": "nodemon src/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": "^8.13.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"winston": "^3.8.0",
|
||||
"express": "^4.18.2",
|
||||
"axios": "^1.4.0",
|
||||
"sqlite3": "^5.1.6",
|
||||
"basic-auth": "^2.0.1"
|
||||
"sqlite3": "^5.1.6"
|
||||
}
|
||||
}
|
||||
|
||||
+4
-49
@@ -1,50 +1,5 @@
|
||||
const express = require('express');
|
||||
const basicAuth = require('basic-auth');
|
||||
const logger = require('./logger');
|
||||
// The admin console was intentionally removed from the project per repository owner request.
|
||||
// If you need to restore it later, re-add an admin HTTP server (e.g., using express) and
|
||||
// provide ADMIN_USER / ADMIN_PASS environment variables for basic auth.
|
||||
|
||||
let serverInstance;
|
||||
|
||||
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 };
|
||||
module.exports = {};
|
||||
|
||||
+15
-6
@@ -8,16 +8,28 @@ const logger = require('./logger');
|
||||
const protocol = require('./protocol');
|
||||
const auth = require('./auth');
|
||||
const storage = require('./storage');
|
||||
const admin = require('./admin');
|
||||
|
||||
const PORT = process.env.PORT || 5055;
|
||||
const ADMIN_PORT = process.env.ADMIN_PORT || 5056;
|
||||
|
||||
(async () => {
|
||||
await storage.init();
|
||||
|
||||
// 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 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}`));
|
||||
|
||||
// Start admin console (HTTP) bound to ADMIN_PORT
|
||||
admin.start({ port: ADMIN_PORT, getConnections: () => connections });
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user