memo/server.js
2023-03-05 10:19:34 +03:00

871 lines
22 KiB
JavaScript

// vim:set noexpandtab:
/**************
SYSTEM INCLUDES
**************/
var http = require("http");
var reload = require("reload");
var sanitizer = require("sanitizer");
var compression = require("compression");
var express = require("express");
var conf = require("./config.js").server;
var headerBarUrl = require("./config.js").headerBarUrl;
var logoUrl = require("./config.js").logoUrl;
/**************
LOCAL INCLUDES
**************/
var rooms = require("./lib/rooms.js");
var data = require("./lib/data.js").db;
/**************
GLOBALS
**************/
//Map of sids to user_names
var sids_to_user_names = [];
/**************
SETUP EXPRESS
**************/
var app = express();
var router = express.Router();
app.use(compression());
app.use(conf.baseurl, router);
router.use(express.static(__dirname + "/node_modules"));
router.use(express.static(__dirname + "/client"));
var server = http.createServer(app);
// Reload code here
reload(app)
.then(function (reloadReturned) {
// reloadReturned is documented in the returns API in the README
// Reload started, start web server
server.listen(conf.port, function () {
console.log("Web server available on http://127.0.0.1:" + conf.port);
});
})
.catch(function (err) {
console.error(
"Reload could not start, could not start server/sample app",
err
);
});
/**************
SETUP Socket.IO
**************/
var io = require("socket.io")(server, {
path: conf.baseurl == "/" ? "" : conf.baseurl + "/socket.io",
cookie: false
});
/**************
ROUTES
**************/
router.get("/", function (req, res) {
//console.log(req.header('host'));
url = req.header("host") + req.baseUrl;
var connected = io.sockets.connected;
clientsCount = Object.keys(connected).length;
res.render("home.jade", {
url: url,
headerBarUrl: headerBarUrl,
logoUrl: logoUrl,
connected: clientsCount,
home: true,
});
});
router.get("/demo", function (req, res) {
url = req.header("host") + req.baseUrl;
res.render("index.jade", {
pageTitle: "Post-it - demo",
headerBarUrl: headerBarUrl,
logoUrl: logoUrl,
url: url,
demo: true,
});
});
router.get("/:id", function (req, res) {
url = req.header("host") + req.baseUrl;
res.render("index.jade", {
pageTitle: "Post-it - " + req.params.id,
headerBarUrl: headerBarUrl,
logoUrl: logoUrl,
url: url,
});
});
router.get("/stats", function (req, res) {
console.log("TODO: stats");
});
/**************
SOCKET.I0
**************/
//sanitizes text
function scrub(text) {
if (typeof text != "undefined" && text !== null) {
//clip the string if it is too long
if (text.length > 65535) {
text = text.substr(0, 65535);
}
return sanitizer.sanitize(text);
} else {
return null;
}
}
io.sockets.on("connection", function (client) {
client.on("message", function (message) {
//console.log(message.action + " -- " + sys.inspect(message.data) );
var clean_data = {};
var clean_message = {};
var message_out = {};
if (!message.action) return;
switch (message.action) {
case "initializeMe":
initClient(client);
break;
case "joinRoom":
joinRoom(client, message.data, function (clients) {
client.json.send({ action: "roomAccept", data: "" });
});
break;
case "moveCard":
//report to all other browsers
message_out = {
action: message.action,
data: {
id: scrub(message.data.id),
position: {
left: scrub(message.data.position.left),
top: scrub(message.data.position.top),
},
},
};
broadcastToRoom(client, message_out);
// console.log("-----" + message.data.id);
// console.log(JSON.stringify(message.data));
getRoom(client, function (room) {
db.cardSetXY(
room,
message.data.id,
message.data.position.left,
message.data.position.top
);
});
break;
case "createCard":
data = message.data;
clean_data = {};
clean_data.text = scrub(data.text);
clean_data.id = scrub(data.id);
clean_data.x = scrub(data.x);
clean_data.y = scrub(data.y);
clean_data.rot = scrub(data.rot);
clean_data.colour = scrub(data.colour);
getRoom(client, function (room) {
createCard(
room,
clean_data.id,
clean_data.text,
clean_data.x,
clean_data.y,
clean_data.rot,
clean_data.colour
);
});
message_out = {
action: "createCard",
data: clean_data,
};
//report to all other browsers
broadcastToRoom(client, message_out);
break;
case "editCard":
clean_data = {};
clean_data.value = scrub(message.data.value);
clean_data.id = scrub(message.data.id);
//send update to database
getRoom(client, function (room) {
db.cardEdit(room, clean_data.id, clean_data.value);
});
message_out = {
action: "editCard",
data: clean_data,
};
broadcastToRoom(client, message_out);
break;
case "deleteCard":
clean_message = {
action: "deleteCard",
data: { id: scrub(message.data.id) },
};
getRoom(client, function (room) {
db.deleteCard(room, clean_message.data.id);
});
//report to all other browsers
broadcastToRoom(client, clean_message);
break;
case "createColumn":
clean_message = { data: scrub(message.data) };
getRoom(client, function (room) {
db.createColumn(room, clean_message.data, function () {});
});
broadcastToRoom(client, clean_message);
break;
case "deleteColumn":
getRoom(client, function (room) {
db.deleteColumn(room);
});
broadcastToRoom(client, { action: "deleteColumn" });
break;
case "updateColumns":
var columns = message.data;
if (!(columns instanceof Array)) break;
var clean_columns = [];
for (var i in columns) {
clean_columns[i] = scrub(columns[i]);
}
getRoom(client, function (room) {
db.setColumns(room, clean_columns);
});
broadcastToRoom(client, {
action: "updateColumns",
data: clean_columns,
});
break;
case "changeTheme":
clean_message = {};
clean_message.data = scrub(message.data);
getRoom(client, function (room) {
db.setTheme(room, clean_message.data);
});
clean_message.action = "changeTheme";
broadcastToRoom(client, clean_message);
break;
case "setUserName":
clean_message = {};
clean_message.data = scrub(message.data);
setUserName(client, clean_message.data);
var msg = {};
msg.action = "nameChangeAnnounce";
msg.data = { sid: client.id, user_name: clean_message.data };
broadcastToRoom(client, msg);
break;
case "addSticker":
var cardId = scrub(message.data.cardId);
var stickerId = scrub(message.data.stickerId);
getRoom(client, function (room) {
db.addSticker(room, cardId, stickerId);
});
broadcastToRoom(client, {
action: "addSticker",
data: { cardId: cardId, stickerId: stickerId },
});
break;
case "setBoardSize":
var size = {};
size.width = scrub(message.data.width);
size.height = scrub(message.data.height);
getRoom(client, function (room) {
db.setBoardSize(room, size);
});
broadcastToRoom(client, { action: "setBoardSize", data: size });
break;
case "exportTxt":
exportBoard("txt", client, message.data);
break;
case "exportCsv":
exportBoard("csv", client, message.data);
break;
case "exportJson":
exportJson(client, message.data);
break;
case "importJson":
importJson(client, message.data);
break;
case "createRevision":
createRevision(client, message.data);
break;
case "deleteRevision":
deleteRevision(client, message.data);
break;
case "exportRevision":
exportRevision(client, message.data);
break;
default:
//console.log('unknown action');
break;
}
});
client.on("disconnect", function () {
leaveRoom(client);
});
//tell all others that someone has connected
//client.broadcast('someone has connected');
});
/**************
FUNCTIONS
**************/
function initClient(client) {
//console.log ('initClient Started');
getRoom(client, function (room) {
db.getAllCards(room, function (cards) {
client.json.send({
action: "initCards",
data: cards,
});
});
db.getAllColumns(room, function (columns) {
client.json.send({
action: "initColumns",
data: columns,
});
});
db.getRevisions(room, function (revisions) {
client.json.send({
action: "initRevisions",
data: revisions !== null ? Object.keys(revisions) : new Array(),
});
});
db.getTheme(room, function (theme) {
if (theme === null) theme = "bigcards";
client.json.send({
action: "changeTheme",
data: theme,
});
});
db.getBoardSize(room, function (size) {
if (size !== null) {
client.json.send({
action: "setBoardSize",
data: size,
});
}
});
roommates_clients = rooms.room_clients(room);
roommates = [];
var j = 0;
for (var i in roommates_clients) {
if (roommates_clients[i].id != client.id) {
roommates[j] = {
sid: roommates_clients[i].id,
user_name: sids_to_user_names[roommates_clients[i].id],
};
j++;
}
}
//console.log('initialusers: ' + roommates);
client.json.send({
action: "initialUsers",
data: roommates,
});
});
}
function joinRoom(client, room, successFunction) {
var msg = {};
msg.action = "join-announce";
msg.data = { sid: client.id, user_name: client.user_name };
rooms.add_to_room_and_announce(client, room, msg);
successFunction();
}
function leaveRoom(client) {
//console.log (client.id + ' just left');
var msg = {};
msg.action = "leave-announce";
msg.data = { sid: client.id };
rooms.remove_from_all_rooms_and_announce(client, msg);
delete sids_to_user_names[client.id];
}
function broadcastToRoom(client, message) {
rooms.broadcast_to_roommates(client, message);
}
//----------------CARD FUNCTIONS
function createCard(room, id, text, x, y, rot, colour) {
var card = {
id: id,
colour: colour,
rot: rot,
x: x,
y: y,
text: text,
sticker: null,
};
db.createCard(room, id, card);
}
function roundRand(max) {
return Math.floor(Math.random() * max);
}
//------------ROOM STUFF
// Get Room name for the given Session ID
function getRoom(client, callback) {
room = rooms.get_room(client);
//console.log( 'client: ' + client.id + " is in " + room);
callback(room);
}
function setUserName(client, name) {
client.user_name = name;
sids_to_user_names[client.id] = name;
//console.log('sids to user names: ');
console.dir(sids_to_user_names);
}
function cleanAndInitializeDemoRoom() {
// DUMMY DATA
db.clearRoom("/demo", function () {
db.createColumn("/demo", "Pas commencé");
db.createColumn("/demo", "Commencé");
db.createColumn("/demo", "En test");
db.createColumn("/demo", "Validation");
db.createColumn("/demo", "Terminé");
createCard(
"/demo",
"card1",
"Salut, c'est fun",
roundRand(600),
roundRand(300),
Math.random() * 10 - 5,
"yellow"
);
createCard(
"/demo",
"card2",
"Salut, c'est une nouvelle histoire.",
roundRand(600),
roundRand(300),
Math.random() * 10 - 5,
"white"
);
createCard(
"/demo",
"card3",
".",
roundRand(600),
roundRand(300),
Math.random() * 10 - 5,
"blue"
);
createCard(
"/demo",
"card4",
".",
roundRand(600),
roundRand(300),
Math.random() * 10 - 5,
"green"
);
createCard(
"/demo",
"card5",
"Salut, c'est fun",
roundRand(600),
roundRand(300),
Math.random() * 10 - 5,
"yellow"
);
createCard(
"/demo",
"card6",
"Salut, c'est un nouveau mémo.",
roundRand(600),
roundRand(300),
Math.random() * 10 - 5,
"yellow"
);
createCard(
"/demo",
"card7",
".",
roundRand(600),
roundRand(300),
Math.random() * 10 - 5,
"blue"
);
createCard(
"/demo",
"card8",
".",
roundRand(600),
roundRand(300),
Math.random() * 10 - 5,
"green"
);
});
}
// Export board in txt or csv
function exportBoard(format, client, data) {
var result = new Array();
getRoom(client, function (room) {
db.getAllCards(room, function (cards) {
db.getAllColumns(room, function (columns) {
var text = new Array();
var cols = {};
if (columns.length > 0) {
for (var i = 0; i < columns.length; i++) {
cols[columns[i]] = new Array();
for (var j = 0; j < cards.length; j++) {
if (i === 0) {
if (cards[j]["x"] < (i + 1) * data) {
cols[columns[i]].push(cards[j]);
}
} else if (i + 1 === columns.length) {
if (cards[j]["x"] >= i * data) {
cols[columns[i]].push(cards[j]);
}
} else if (
cards[j]["x"] >= i * data &&
cards[j]["x"] < (i + 1) * data
) {
cols[columns[i]].push(cards[j]);
}
}
cols[columns[i]].sort(function (a, b) {
if (a["y"] === b["y"]) {
return a["x"] - b["x"];
} else {
return a["y"] - b["y"];
}
});
}
if (format === "txt") {
for (var i = 0; i < columns.length; i++) {
if (i === 0) {
text.push("# " + columns[i]);
} else {
text.push("\n# " + columns[i]);
}
for (var j = 0; j < cols[columns[i]].length; j++) {
text.push("- " + cols[columns[i]][j]["text"]);
}
}
} else if (format === "csv") {
var max = 0;
var line = new Array();
var patt_vuln = new RegExp("^[=+-@]");
for (var i = 0; i < columns.length; i++) {
if (cols[columns[i]].length > max) {
max = cols[columns[i]].length;
}
var val = columns[i].replace(/"/g, '""');
if (patt_vuln.test(val)) {
// prevent CSV Formula Injection
var val = "'" + val;
}
line.push('"' + val + '"');
}
text.push(line.join(","));
for (var j = 0; j < max; j++) {
line = new Array();
for (var i = 0; i < columns.length; i++) {
var val =
cols[columns[i]][j] !== undefined
? cols[columns[i]][j]["text"].replace(/"/g, '""')
: "";
if (patt_vuln.test(val)) {
// prevent CSV Formula Injection
var val = "'" + val;
}
line.push('"' + val + '"');
}
text.push(line.join(","));
}
}
} else {
for (var j = 0; j < cards.length; j++) {
if (format === "txt") {
text.push("- " + cards[j]["text"]);
} else if (format === "csv") {
text.push('"' + cards[j]["text"].replace(/"/g, '""') + '"\n');
}
}
}
var result;
if (format === "txt" || format === "csv") {
result = text.join("\n");
} else if (format === "json") {
result = JSON.stringify(cols);
}
client.json.send({
action: "export",
data: {
filename: room.replace("/", "") + "." + format,
text: result,
},
});
});
});
});
}
// Export board in json, suitable for import
function exportJson(client, data) {
var result = new Array();
getRoom(client, function (room) {
db.getAllCards(room, function (cards) {
db.getAllColumns(room, function (columns) {
db.getTheme(room, function (theme) {
db.getBoardSize(room, function (size) {
if (theme === null) theme = "bigcards";
if (size === null)
size = { width: data.width, height: data.height };
result = JSON.stringify({
cards: cards,
columns: columns,
theme: theme,
size: size,
});
client.json.send({
action: "export",
data: {
filename: room.replace("/", "") + ".json",
text: result,
},
});
});
});
});
});
});
}
// Import board from json
function importJson(client, data) {
getRoom(client, function (room) {
db.clearRoom(room, function () {
db.getAllCards(room, function (cards) {
for (var i = 0; i < cards.length; i++) {
db.deleteCard(room, cards[i].id);
}
cards = data.cards;
var cards2 = new Array();
for (var i = 0; i < cards.length; i++) {
var card = cards[i];
if (
card.id !== undefined &&
card.colour !== undefined &&
card.rot !== undefined &&
card.x !== undefined &&
card.y !== undefined &&
card.text !== undefined &&
card.sticker !== undefined
) {
var c = {
id: card.id,
colour: card.colour,
rot: card.rot,
x: card.x,
y: card.y,
text: scrub(card.text),
sticker: card.sticker,
};
db.createCard(room, c.id, c);
cards2.push(c);
}
}
var msg = { action: "initCards", data: cards2 };
broadcastToRoom(client, msg);
client.json.send(msg);
});
db.getAllColumns(room, function (columns) {
for (var i = 0; i < columns.length; i++) {
db.deleteColumn(room);
}
columns = data.columns;
var columns2 = new Array();
for (var i = 0; i < columns.length; i++) {
var column = scrub(columns[i]);
if (typeof column === "string") {
db.createColumn(room, column);
columns2.push(column);
}
}
msg = { action: "initColumns", data: columns2 };
broadcastToRoom(client, msg);
client.json.send(msg);
});
var size = data.size;
if (size.width !== undefined && size.height !== undefined) {
size = { width: scrub(size.width), height: scrub(size.height) };
db.setBoardSize(room, size);
msg = { action: "setBoardSize", data: size };
broadcastToRoom(client, msg);
client.json.send(msg);
}
data.theme = scrub(data.theme);
if (data.theme === "smallcards" || data.theme === "bigcards") {
db.setTheme(room, data.theme);
msg = { action: "changeTheme", data: data.theme };
broadcastToRoom(client, msg);
client.json.send(msg);
}
});
});
}
//
function createRevision(client, data) {
var result = new Array();
getRoom(client, function (room) {
db.getAllCards(room, function (cards) {
db.getAllColumns(room, function (columns) {
db.getTheme(room, function (theme) {
db.getBoardSize(room, function (size) {
if (theme === null) theme = "bigcards";
if (size === null)
size = { width: data.width, height: data.height };
result = {
cards: cards,
columns: columns,
theme: theme,
size: size,
};
var timestamp = Date.now();
db.getRevisions(room, function (revisions) {
if (revisions === null) revisions = {};
revisions[timestamp + ""] = result;
db.setRevisions(room, revisions);
msg = { action: "addRevision", data: timestamp };
broadcastToRoom(client, msg);
client.json.send(msg);
});
});
});
});
});
});
}
function deleteRevision(client, timestamp) {
getRoom(client, function (room) {
db.getRevisions(room, function (revisions) {
if (revisions !== null && revisions[timestamp + ""] !== undefined) {
delete revisions[timestamp + ""];
db.setRevisions(room, revisions);
}
msg = { action: "deleteRevision", data: timestamp };
broadcastToRoom(client, msg);
client.json.send(msg);
});
});
}
function exportRevision(client, timestamp) {
getRoom(client, function (room) {
db.getRevisions(room, function (revisions) {
if (revisions !== null && revisions[timestamp + ""] !== undefined) {
client.json.send({
action: "export",
data: {
filename: room.replace("/", "") + "-" + timestamp + ".json",
text: JSON.stringify(revisions[timestamp + ""]),
},
});
} else {
client.json.send({
action: "message",
data: "Unable to find revision " + timestamp + ".",
});
}
});
});
}
/**************
SETUP DATABASE ON FIRST RUN
**************/
// (runs only once on startup)
var db = new data(function () {
cleanAndInitializeDemoRoom();
});