diff --git a/client/package.json b/client/package.json
index 8e931a5..54e7db0 100644
--- a/client/package.json
+++ b/client/package.json
@@ -9,7 +9,9 @@
"@testing-library/jest-dom": "^5.16.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
+ "http-proxy-middleware": "^2.0.3",
"moment": "^2.29.1",
+ "moment-timezone": "^0.5.34",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-moment": "^1.1.1",
diff --git a/client/src/Table.js b/client/src/Table.js
index e2b22a7..4d1ba0f 100755
--- a/client/src/Table.js
+++ b/client/src/Table.js
@@ -917,7 +917,31 @@ class Table extends React.Component {
componentDidMount() {
this.start = new Date();
- const params = {};
+ console.log(`Mounted: ${base}`);
+ let loc = window.location, new_uri;
+ if (loc.protocol === "https:") {
+ new_uri = "wss";
+ } else {
+ new_uri = "ws";
+ }
+ new_uri = `${new_uri}://${loc.host}${base}/ws`;
+ this.ws = new WebSocket(new_uri);
+
+ this.ws.onopen = (event) => {
+ console.log(event);
+ //ws.send(JSON.stringify(apiCall));
+ };
+
+ this.ws.onmessage = (event) => {
+ const json = JSON.parse(event.data);
+ console.log(json);
+ };
+
+ this.ws.onerror = (event) => {
+ console.error(event);
+ };
+
+ const params = {};
if (this.id) {
console.log(`Loading game: ${this.id}`);
params.url = `${base}/api/v1/games/${this.id}`;
@@ -964,8 +988,6 @@ class Table extends React.Component {
}).then((res) => {
return res.json();
}).then((game) => {
-// console.log (`Game ${game.id} loaded ${moment().format()}.`);
-
if (!this.id) {
history.push(`${gamesPath}/${game.id}`);
}
diff --git a/client/src/setupProxy.js b/client/src/setupProxy.js
new file mode 100644
index 0000000..c17d3e5
--- /dev/null
+++ b/client/src/setupProxy.js
@@ -0,0 +1,12 @@
+const { createProxyMiddleware } = require('http-proxy-middleware');
+
+module.exports = function(app) {
+ const base = process.env.PUBLIC_URL;
+ console.log('http-proxy-middleware');
+ app.use(createProxyMiddleware(
+ `${base}/ws`, {
+ ws: true,
+ target: 'http://localhost:8930',
+ changeOrigin: true,
+ }));
+};
diff --git a/server/app.js b/server/app.js
index 381265a..afce3dd 100755
--- a/server/app.js
+++ b/server/app.js
@@ -5,7 +5,6 @@ process.env.TZ = "Etc/GMT";
console.log("Loading ketr.ketran");
const express = require("express"),
- morgan = require("morgan"),
bodyParser = require("body-parser"),
config = require("config"),
session = require('express-session'),
@@ -15,7 +14,6 @@ const express = require("express"),
require("./console-line.js"); /* Monkey-patch console.log with line numbers */
-
const frontendPath = config.get("frontendPath").replace(/\/$/, "") + "/",
serverConfig = config.get("server");
@@ -25,51 +23,27 @@ let userDB, gameDB;
const app = express();
-app.set("basePath", basePath);
+app.use(bodyParser.json());
+
/* App is behind an nginx proxy which we trust, so use the remote address
* set in the headers */
app.set("trust proxy", true);
+app.set("basePath", basePath);
app.use(basePath, require("./routes/basepath.js"));
/* Handle static files first so excessive logging doesn't occur */
app.use(basePath, express.static(frontendPath, { index: false }));
-app.use(bodyParser.json());
+/*
app.use(bodyParser.urlencoded({
extended: false
}));
-
-/* *******************************************************************************
- * Logging - begin
- *
- * This runs before after cookie parsing, but before routes. If we set
- * immediate: true on the morgan options, it happens before cookie parsing
- * */
-const logging = false;
-
-if (logging) morgan.token('remote-user', function (req) {
- return req.user ? req.user.username : "N/A";
-});
-
-/* Any path starting with the following won't be logged via morgan */
-const logSkipPaths = new RegExp("^" + basePath + "(" + [
- ".*thumbs\\/",
- "bower_components",
-].join(")|(") + ")");
-if (logging) app.use(morgan('common', {
- skip: function (req) {
- return logSkipPaths.exec(req.originalUrl);
- }
-}));
-
-/*
- * Logging - end
- * ******************************************************************************* */
+*/
/* body-parser does not support text/*, so add support for that here */
-app.use(function(req, res, next){
+if (0) app.use(function(req, res, next){
if (!req.is('text/*')) {
return next();
}
@@ -84,13 +58,15 @@ app.use(function(req, res, next){
});
});
-app.use(session({
+const sessionParser = session({
store: new SQLiteStore({ db: config.get("sessions.db") }),
secret: config.get("sessions.store-secret"),
cookie: { maxAge: 21 * 24 * 60 * 60 * 1000 }, // 3 weeks
saveUninitialized: false,
resave: true
-}));
+});
+
+app.use(sessionParser);
const index = require("./routes/index");
@@ -99,146 +75,17 @@ if (config.has("admin")) {
app.set("admin", admin);
}
-if (config.has("admin.mail") &&
- config.has("smtp.host") &&
- config.has("smtp.sender")) {
- app.set("transporter", require("nodemailer").createTransport({
- host: config.get("smtp.host"),
- pool: true,
- port: config.has("smtp.port") ? config.get("smtp.port") : 25
- }));
-} else {
- console.log("SMTP disabled. To enable SMTP, configure admin.mail, smtp.host, and smtp.sender");
-}
-
-const templates = {
- "html": [
- "
The user {{displayName}} has verified their email address ({{mail}}).
",
- "",
- "They indicated they know:
",
- "{{notes}}
",
- "",
- "To authenticate:
",
- "echo 'UPDATE users SET authenticated=1 WHERE id={{id}};' | sqlite3 users.db
",
- "",
- "Sincerely,
",
- "James
"
- ].join("\n"),
- "text": [
- "The user {{displayName}} has verified their email address ({{mail}}).",
- "",
- "They indicated they know:",
- "{{notes}}",
- "",
- "To authenticate:",
- "echo 'UPDATE users SET authenticated=1 WHERE id={{id}};' | sqlite3 users.db",
- "",
- "Sincerely,",
- "James"
- ].join("\n")
-};
-
-/* Look for action-token URLs and process; this does not require a user to be logged
- * in */
-app.use(basePath, function(req, res, next) {
- let match = req.url.match(/^\/([0-9a-f]+)$/);
- if (!match) {
- return next();
- }
-
- let key = match[1];
- return userDB.sequelize.query("SELECT * FROM authentications WHERE key=:key", {
- replacements: {
- key: key
- },
- type: userDB.sequelize.QueryTypes.SELECT
- }).then(function(results) {
- let token;
- if (results.length == 0) {
- console.log("Invalid key. Ignoring.");
- return next();
- }
-
- token = results[0];
-
- console.log("Matched token: " + JSON.stringify(token, null, 2));
- switch (token.type) {
- case "account-setup":
- return userDB.sequelize.query("UPDATE users SET mailVerified=1 WHERE id=:userId", {
- replacements: token
- }).then(function() {
- return userDB.sequelize.query("DELETE FROM authentications WHERE key=:key", {
- replacements: token
- });
- }).then(function() {
- return userDB.sequelize.query("SELECT * FROM users WHERE id=:userId", {
- replacements: token,
- type: userDB.sequelize.QueryTypes.SELECT
- }).then(function(results) {
- if (results.length == 0) {
- throw "DB mis-match between authentications and users table";
- }
- const transporter = app.get("transporter");
- if (!transporter) {
- console.log("Not sending VERIFIED email; SMTP not configured.");
- return;
- }
-
- let user = results[0],
- envelope = {
- to: config.get("admin.mail"),
- from: config.get("smtp.sender"),
- subject: "VERIFIED: Account'" + user.displayName + "'",
- cc: "",
- bcc: "",
- text: hb.compile(templates.text)(user),
- html: hb.compile(templates.html)(user)
- };
-
- req.session.userId = user.id;
-
- return new Promise(function (resolve, reject) {
- let attempts = 10;
-
- function send(envelope) {
- /* Rate limit to ten per second */
- transporter.sendMail(envelope, function (error, info) {
- if (!error) {
- console.log('Message sent: ' + info.response);
- return resolve();
- }
-
- if (attempts == 0) {
- console.log("Error sending email: ", error)
- return reject(error);
- }
-
- attempts--;
- console.log("Unable to send mail. Trying again in 100ms (" + attempts + " attempts remain): ", error);
- setTimeout(send.bind(undefined, envelope), 100);
- });
- }
-
- send(envelope);
- });
- }).then(function() {
- return res.redirect(308, basePath);
- });
- });
- }
-
- return next();
- });
-});
-
/* Allow loading of the app w/out being logged in */
app.use(basePath, index);
+
/* /games loads the default index */
app.use(basePath + "games", index);
/* Allow access to the 'users' API w/out being logged in */
+/*
const users = require("./routes/users");
app.use(basePath + "api/v1/users", users.router);
+*/
app.use(function(err, req, res, next) {
console.error(err.message);
@@ -266,7 +113,6 @@ app.use(basePath, function(req, res, next) {
/* Everything below here requires a successful authentication */
app.use(basePath, express.static(frontendPath, { index: false }));
-
app.use(basePath + "api/v1/games", require("./routes/games"));
/* Declare the "catch all" index route last; the final route is a 404 dynamic router */
@@ -278,6 +124,7 @@ app.use(basePath, index);
app.set("port", serverConfig.port);
const server = require("http").createServer(app);
+
process.on('SIGINT', () => {
server.close(() => {
console.log("Gracefully shutting down from SIGINT (Ctrl-C)");
@@ -285,6 +132,8 @@ process.on('SIGINT', () => {
});
});
+const WebSocket = require('ws');
+
require("./db/games").then(function(db) {
gameDB = db;
}).then(function() {
@@ -293,7 +142,36 @@ require("./db/games").then(function(db) {
});
}).then(function() {
console.log("DB connected. Opening server.");
- server.listen(serverConfig.port);
+
+ /* Create web socket server on top of a regular http server */
+ const ws = new WebSocket.Server({
+ server
+ });
+
+ /* Mount the Express app here */
+
+ //server.on('request', app);
+
+ app.set('ws', ws);
+
+ ws.on('connection', (req) => {/*
+ sessionParser(req.upgradeReq, {}, () => {
+ console.log("New websocket connection:");
+ var sess = req.upgradeReq.session;
+ console.log("working = " + sess.working);
+ });*/
+ });
+
+ ws.on('message', (message) => {
+ console.log(`received: ${message}`);
+ ws.send(JSON.stringify({
+ answer: 42
+ }));
+ });
+
+ server.listen(serverConfig.port, () => {
+ console.log(`http/ws server listening on ${serverConfig.port}`);
+ });
}).catch(function(error) {
console.error(error);
process.exit(-1);
@@ -318,7 +196,3 @@ server.on("error", function(error) {
throw error;
}
});
-
-server.on("listening", function() {
- console.log("Listening on " + serverConfig.port);
-});
diff --git a/server/http-server.js b/server/http-server.js
deleted file mode 100644
index 8ad8e46..0000000
--- a/server/http-server.js
+++ /dev/null
@@ -1,137 +0,0 @@
-
-"use strict";
-
-const express = require('express'),
- morgan = require('morgan'),
- cookieParser = require('cookie-parser'),
- bodyParser = require('body-parser'),
- http = require('http'),
- config = require('config'),
- app = express(),
- { timestamp } = require('./timestamp'),
- fs = require('fs'),
- util = require('util'),
- mkdir = util.promisify(fs.mkdir),
- unlink = util.promisify(fs.unlink),
- path = require('path'),
- fetch = require('node-fetch'),
- Promise = require('bluebird'),
- url = require('url'),
- { exec } = require('child_process');
-
-fetch.Promise = Promise;
-
-const basePath = "/" + config.get("http.base").replace(/^\/*/, "").replace(/\/*$/, "") + "/",
- dataPath = "/" + config.get("dataPath").replace(/^\/*/, "").replace(/\/*$/, "") + "/";
-/* */
-
-if (!config.has("auth.idsid") || !config.has("auth.password")) {
- console.error("You need to provide credentials to connect to ubit-gfx in config/local.json");
- console.error(' "auth": { "idsid": "USERNAME", "password": "PASSWORD" }');
- process.exit(-1);
-}
-
-app.use(morgan('common'));
-
-app.use(bodyParser.json({
- verify: function(req,res,buf) {
- req.rawBody = buf;
- }
-}));
-
-app.use(bodyParser.urlencoded({
- extended: false
-}));
-app.use(cookieParser());
-
-/* Routes:
- * /api/v1/publish Publish content to repository
- */
-
-app.get("/*", (req, res, next) => {
- /* */
- return res.status(400).send({ usage: `POST ${basePath}api/v1/publish/:distro/:releaseStream/:url` });
-});
-
-const auth = new Buffer(config.get("auth.idsid") + ":" + config.get("auth.password"), 'ascii').toString('base64');
-
-app.post(basePath + 'api/v1/publish/:distro/:releaseStream/:url', function (req, res, next) {
- const distro = req.params.distro,
- releaseStream = req.params.releaseStream,
- remoteUrl = req.params.url;
- let filename;
-
- try {
- filename = path.basename(url.parse(remoteUrl).pathname);
- } catch (error) {
- return res.status(400).send({ error: `Unparsable URL: ${remoteUrl}` });
- }
-
- if (distro.match(/\//) || releaseStream.match(/\//)) {
- return res.status(400).send({ error: "Neither distro nor releaseStream may contain '/'" });
- }
-
- console.log(`POST publish/${distro}-${releaseStream}/${filename}`);
-
- const filepath = `${dataPath}${distro}-${releaseStream}`;
-
- return mkdir(filepath, { recursive: true }, () => {
- const pathname = `${filepath}/${filename}`;
- if (fs.existsSync(pathname)) {
- return res.status(409).send({ message: `'${distro}-${releaseStream}/${filename}' already exists.` });
- }
-
- return fetch(remoteUrl, {
- method: "GET",
- headers: {
- 'Authorization': `Basic ${auth}`
- }
- }).then(result => {
- const dest = fs.createWriteStream(pathname);
- dest.on('finish', () => {
- exec(`./update-repository.sh ${distro}-${releaseStream}`, {
- cwd: ".." ,
- shell: "/bin/bash"
- }, (error, stdout, stderr) => {
- if (error) {
- return unlink(pathname).catch(() => {
- console.error(`Unable to remove ${pathname} after update-repository.sh failed.`);
- }).then(() => {
- return res.status(500).send({ message: "Error while updating aptly database.", error: error, stderr: stderr, stdout: stdout });
- });
- }
- return res.status(200).send({ message: "OK", stdout: stdout || "", stderr: stderr || "" });
- });
- });
- result.body.pipe(dest);
- }).catch((error )=> {
- const message = `Unable to download ${remoteUrl}: ${error}`;
- console.error(message);
- return res.status(500).send({ message: message });
- });
- }).catch((error) => {
- const message = `Unable to mkdir ${filepath}: ${error}`;
- console.error(message);
- return res.status(500).send({ message: message });
- });
-});
-
-app.post("/*", (req, res, next) => {
- /* */
- return res.status(400).send({ usage: `POST /${basePath}/api/v1/publish/:distro/:releaseStream/:url` });
-});
-
-
-const server = http.createServer(app),
- port = config.has("port") ? config.get("port") : 6543;
-
-server.listen(port);
-server.on('listening', function() {
- let addr = server.address();
- let bind = typeof addr === 'string'
- ? 'pipe ' + addr
- : 'port ' + addr.port;
- console.log(timestamp() + ` Now serving ${basePath} on ${bind}`);
-});
-
-module.exports = server;
diff --git a/server/package.json b/server/package.json
index a1b1a90..d583eac 100644
--- a/server/package.json
+++ b/server/package.json
@@ -26,7 +26,8 @@
"random-words": "^1.1.2",
"sequelize": "^5.21.6",
"sqlite3": "^4.1.1",
- "typeface-roboto": "0.0.75"
+ "typeface-roboto": "0.0.75",
+ "ws": "^8.5.0"
},
"repository": {
"type": "git",
diff --git a/server/routes/basepath.js b/server/routes/basepath.js
index a8fb0bb..eba896d 100644
--- a/server/routes/basepath.js
+++ b/server/routes/basepath.js
@@ -8,7 +8,7 @@ const router = express.Router();
/* This router only handles HTML files and is used
* to replace BASEPATH */
-router.get("/*", function(req, res, next) {
+router.get("/*", (req, res, next) => {
const parts = url.parse(req.url),
basePath = req.app.get("basePath");