2020-11-07 11:35:29 +01:00
|
|
|
import WebSocketServer from './WebSocketServer.js'
|
|
|
|
|
|
2020-11-25 22:03:35 +01:00
|
|
|
import fs from 'fs'
|
2020-11-07 11:35:29 +01:00
|
|
|
import express from 'express'
|
2020-11-25 22:03:35 +01:00
|
|
|
import multer from 'multer'
|
2020-11-07 12:21:38 +01:00
|
|
|
import config from './config.js'
|
2020-11-17 21:46:56 +01:00
|
|
|
import Protocol from './../common/Protocol.js'
|
2020-11-12 19:25:42 +01:00
|
|
|
import Util from './../common/Util.js'
|
2020-11-12 19:19:02 +01:00
|
|
|
import Game from './Game.js'
|
2020-11-25 22:03:35 +01:00
|
|
|
import twing from 'twing'
|
|
|
|
|
import bodyParser from 'body-parser'
|
2020-11-12 19:19:02 +01:00
|
|
|
|
2020-11-25 22:03:35 +01:00
|
|
|
const allImages = () => [
|
|
|
|
|
...fs.readdirSync('./../game/example-images/').map(f => ({
|
|
|
|
|
file: `./../game/example-images/${f}`,
|
|
|
|
|
url: `/example-images/${f}`,
|
|
|
|
|
})),
|
|
|
|
|
...fs.readdirSync('./../data/uploads/').map(f => ({
|
|
|
|
|
file: `./../data/uploads/${f}`,
|
|
|
|
|
url: `/uploads/${f}`,
|
|
|
|
|
})),
|
2020-11-08 14:13:43 +01:00
|
|
|
]
|
|
|
|
|
|
2020-11-07 12:21:38 +01:00
|
|
|
const port = config.http.port
|
|
|
|
|
const hostname = config.http.hostname
|
2020-11-07 11:35:29 +01:00
|
|
|
const app = express()
|
2020-11-25 22:03:35 +01:00
|
|
|
|
|
|
|
|
const uploadDir = './../data/uploads'
|
|
|
|
|
const storage = multer.diskStorage({
|
|
|
|
|
destination: uploadDir,
|
|
|
|
|
filename: function (req, file, cb) {
|
|
|
|
|
cb(null , file.originalname);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
const upload = multer({storage}).single('file');
|
|
|
|
|
|
2020-11-07 12:21:38 +01:00
|
|
|
const statics = express.static('./../game/')
|
2020-11-25 22:03:35 +01:00
|
|
|
|
|
|
|
|
const render = async (template, data) => {
|
|
|
|
|
const loader = new twing.TwingLoaderFilesystem('./../game/templates')
|
|
|
|
|
const env = new twing.TwingEnvironment(loader)
|
|
|
|
|
return env.render(template, data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
app.use('/g/:gid', async (req, res, next) => {
|
|
|
|
|
res.send(await render('game.html.twig', {
|
|
|
|
|
GAME_ID: req.params.gid,
|
|
|
|
|
WS_ADDRESS: config.ws.connectstring,
|
|
|
|
|
}))
|
|
|
|
|
})
|
|
|
|
|
app.post('/upload', (req, res) => {
|
|
|
|
|
upload(req, res, (err) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
console.log(err)
|
|
|
|
|
res.status(400).send("Something went wrong!");
|
|
|
|
|
}
|
|
|
|
|
res.send({
|
|
|
|
|
image: {
|
|
|
|
|
file: './../data/uploads/' + req.file.filename,
|
|
|
|
|
url: '/uploads/' + req.file.filename,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
app.post('/newgame', bodyParser.json(), async (req, res) => {
|
|
|
|
|
console.log(req.body.tiles, req.body.image)
|
|
|
|
|
const gameId = Util.uniqId()
|
|
|
|
|
if (!Game.exists(gameId)) {
|
|
|
|
|
await Game.createGame(gameId, req.body.tiles, req.body.image)
|
|
|
|
|
}
|
|
|
|
|
res.send({ url: `/g/${gameId}` })
|
2020-11-08 14:49:34 +01:00
|
|
|
})
|
|
|
|
|
|
2020-11-12 19:19:02 +01:00
|
|
|
app.use('/common/', express.static('./../common/'))
|
2020-11-25 22:03:35 +01:00
|
|
|
app.use('/uploads/', express.static('./../data/uploads/'))
|
|
|
|
|
app.use('/', async (req, res, next) => {
|
2020-11-07 12:21:38 +01:00
|
|
|
if (req.path === '/') {
|
2020-11-25 22:03:35 +01:00
|
|
|
const games = [
|
|
|
|
|
...Object.keys(Game.getAllGames()).map(id => ({
|
|
|
|
|
id: id,
|
|
|
|
|
title: id,
|
|
|
|
|
})),
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
res.send(await render('index.html.twig', {
|
|
|
|
|
games,
|
|
|
|
|
images: allImages(),
|
|
|
|
|
}))
|
2020-11-07 12:21:38 +01:00
|
|
|
} else {
|
|
|
|
|
statics(req, res, next)
|
|
|
|
|
}
|
|
|
|
|
})
|
2020-11-07 11:35:29 +01:00
|
|
|
|
2020-11-07 12:21:38 +01:00
|
|
|
const wss = new WebSocketServer(config.ws);
|
2020-11-07 11:35:29 +01:00
|
|
|
|
2020-11-08 17:11:32 +01:00
|
|
|
const notify = (data, sockets) => {
|
2020-11-17 21:46:56 +01:00
|
|
|
// TODO: throttle?
|
2020-11-08 17:11:32 +01:00
|
|
|
for (let socket of sockets) {
|
|
|
|
|
wss.notifyOne(data, socket)
|
|
|
|
|
}
|
2020-11-07 11:35:29 +01:00
|
|
|
}
|
|
|
|
|
|
2020-11-25 22:03:35 +01:00
|
|
|
wss.on('close', async ({socket}) => {
|
|
|
|
|
const proto = socket.protocol.split('|')
|
|
|
|
|
const clientId = proto[0]
|
|
|
|
|
const gameId = proto[1]
|
|
|
|
|
if (Game.exists(gameId)) {
|
|
|
|
|
Game.removeSocket(gameId, socket)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2020-11-08 14:13:43 +01:00
|
|
|
wss.on('message', async ({socket, data}) => {
|
2020-11-07 11:35:29 +01:00
|
|
|
try {
|
|
|
|
|
const proto = socket.protocol.split('|')
|
2020-11-17 21:46:56 +01:00
|
|
|
const clientId = proto[0]
|
2020-11-12 19:19:02 +01:00
|
|
|
const gameId = proto[1]
|
2020-11-17 21:46:56 +01:00
|
|
|
const msg = JSON.parse(data)
|
|
|
|
|
const msgType = msg[0]
|
|
|
|
|
switch (msgType) {
|
|
|
|
|
case Protocol.EV_CLIENT_INIT: {
|
2020-11-12 19:19:02 +01:00
|
|
|
if (!Game.exists(gameId)) {
|
2020-11-25 22:03:35 +01:00
|
|
|
throw `[game ${gameId} does not exist... ]`
|
2020-11-08 17:11:32 +01:00
|
|
|
}
|
2020-12-04 00:04:47 +01:00
|
|
|
Game.addPlayer(gameId, clientId)
|
|
|
|
|
Game.addSocket(gameId, socket)
|
|
|
|
|
const game = Game.get(gameId)
|
|
|
|
|
notify(
|
|
|
|
|
[Protocol.EV_SERVER_INIT, {
|
|
|
|
|
puzzle: game.puzzle,
|
|
|
|
|
players: game.players,
|
|
|
|
|
evtInfos: game.evtInfos,
|
|
|
|
|
}],
|
|
|
|
|
[socket]
|
|
|
|
|
)
|
2020-11-07 11:35:29 +01:00
|
|
|
} break;
|
|
|
|
|
|
2020-11-17 21:46:56 +01:00
|
|
|
case Protocol.EV_CLIENT_EVENT: {
|
|
|
|
|
const clientSeq = msg[1]
|
|
|
|
|
const clientEvtData = msg[2]
|
2020-12-04 00:04:47 +01:00
|
|
|
Game.addPlayer(gameId, clientId)
|
|
|
|
|
Game.addSocket(gameId, socket)
|
|
|
|
|
|
|
|
|
|
const game = Game.get(gameId)
|
|
|
|
|
notify(
|
|
|
|
|
[Protocol.EV_SERVER_INIT, {
|
|
|
|
|
puzzle: game.puzzle,
|
|
|
|
|
players: game.players,
|
|
|
|
|
evtInfos: game.evtInfos,
|
|
|
|
|
}],
|
|
|
|
|
[socket]
|
|
|
|
|
)
|
|
|
|
|
const changes = Game.handleInput(gameId, clientId, clientEvtData)
|
|
|
|
|
notify(
|
|
|
|
|
[Protocol.EV_SERVER_EVENT, clientId, clientSeq, changes],
|
|
|
|
|
Game.getSockets(gameId)
|
|
|
|
|
)
|
2020-11-07 11:35:29 +01:00
|
|
|
} break;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e)
|
|
|
|
|
}
|
|
|
|
|
})
|
2020-11-08 16:42:59 +01:00
|
|
|
|
2020-11-25 22:03:35 +01:00
|
|
|
Game.loadAllGames()
|
2020-12-04 00:04:47 +01:00
|
|
|
const server = app.listen(
|
|
|
|
|
port,
|
|
|
|
|
hostname,
|
|
|
|
|
() => console.log(`server running on http://${hostname}:${port}`)
|
|
|
|
|
)
|
2020-11-07 11:35:29 +01:00
|
|
|
wss.listen()
|
2020-12-04 00:04:47 +01:00
|
|
|
|
|
|
|
|
// persist games in fixed interval
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
console.log('Persisting games...');
|
|
|
|
|
Game.persistAll()
|
|
|
|
|
}, config.persistence.interval)
|
|
|
|
|
|
|
|
|
|
const gracefulShutdown = (signal) => {
|
|
|
|
|
console.log(`${signal} received...`)
|
|
|
|
|
Game.persistAll()
|
|
|
|
|
server.close()
|
|
|
|
|
wss.close()
|
2020-12-04 21:19:45 +01:00
|
|
|
process.exit()
|
2020-12-04 00:04:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// used by nodemon
|
|
|
|
|
process.once('SIGUSR2', function () {
|
|
|
|
|
gracefulShutdown('SIGUSR2')
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
process.once('SIGINT', function (code) {
|
|
|
|
|
gracefulShutdown('SIGINT')
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
process.once('SIGTERM', function (code) {
|
|
|
|
|
gracefulShutdown('SIGTERM')
|
|
|
|
|
});
|