// vim:set noexpandtab: /************** SYSTEM INCLUDES **************/ var http = require("http") // var sys = require('sys'); var async = require("async") 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 + "/client")) var server = require("http").Server(app) server.listen(conf.port) console.log("Server running at http://127.0.0.1:" + conf.port + "/") /************** SETUP Socket.IO **************/ var io = require("socket.io")(server, { path: conf.baseurl == "/" ? "" : conf.baseurl + "/socket.io", }) /************** 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 }) }) router.get("/demo", function (req, res) { res.render("index.jade", { pageTitle: "Post-it - demo", headerBarUrl: headerBarUrl, logoUrl: logoUrl, demo: true }) }) router.get("/:id", function (req, res) { res.render("index.jade", { pageTitle: "Post-it - " + req.params.id, headerBarUrl: headerBarUrl, logoUrl: logoUrl }) }) /************** 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() })