diff --git a/NetfluxWebsocketSrv.js b/NetfluxWebsocketSrv.js index 13ec51cca..8ce1ab442 100644 --- a/NetfluxWebsocketSrv.js +++ b/NetfluxWebsocketSrv.js @@ -2,13 +2,13 @@ const Crypto = require('crypto'); const LogStore = require('./storage/LogStore'); + const LAG_MAX_BEFORE_DISCONNECT = 30000; const LAG_MAX_BEFORE_PING = 15000; const HISTORY_KEEPER_ID = Crypto.randomBytes(8).toString('hex'); const USE_HISTORY_KEEPER = true; const USE_FILE_BACKUP_STORAGE = true; -const LOG_MESSAGES = false; let dropUser; @@ -17,7 +17,7 @@ const now = function () { return (new Date()).getTime(); }; const sendMsg = function (ctx, user, msg) { try { - if (LOG_MESSAGES) { console.log('<' + JSON.stringify(msg)); } + if (ctx.config.logToStdout) { console.log('<' + JSON.stringify(msg)); } user.socket.send(JSON.stringify(msg)); } catch (e) { console.log(e.stack); @@ -62,6 +62,24 @@ dropUser = function (ctx, user) { if (chan.length === 0) { console.log("Removing empty channel ["+chanName+"]"); delete ctx.channels[chanName]; + + /* Call removeChannel if it is a function and channel removal is + set to true in the config file */ + if (ctx.config.removeChannels) { + if (typeof(ctx.store.removeChannel) === 'function') { + ctx.timeouts[chanName] = setTimeout(function () { + ctx.store.removeChannel(chanName, function (err) { + if (err) { console.error("[removeChannelErr]: %s", err); } + else { + console.log("Deleted channel [%s] history from database...", chanName); + } + }); + }, ctx.config.channelRemovalTimeout); + } else { + console.error("You have configured your server to remove empty channels, " + + "however, the database adaptor you are using has not implemented this behaviour."); + } + } } else { sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chanName, 'Quit: [ dropUser() ]']); } @@ -91,6 +109,12 @@ const handleMessage = function (ctx, user, msg) { let chanName = obj || randName(); sendMsg(ctx, user, [seq, 'JACK', chanName]); let chan = ctx.channels[chanName] = ctx.channels[chanName] || []; + + // prevent removal of the channel if there is a pending timeout + if (ctx.config.removeChannels && ctx.timeouts[chanName]) { + clearTimeout(ctx.timeouts[chanName]); + } + chan.id = chanName; if (USE_HISTORY_KEEPER) { sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'JOIN', chanName]); @@ -153,11 +177,19 @@ const handleMessage = function (ctx, user, msg) { } }; -let run = module.exports.run = function (storage, socketServer) { +let run = module.exports.run = function (storage, socketServer, config) { + /* Channel removal timeout defaults to 60000ms (one minute) */ + config.channelRemovalTimeout = + typeof(config.channelRemovalTimeout) === 'number'? + config.channelRemovalTimeout: + 60000; + let ctx = { users: {}, channels: {}, - store: (USE_FILE_BACKUP_STORAGE) ? LogStore.create('messages.log', storage) : storage + timeouts: {}, + store: (USE_FILE_BACKUP_STORAGE) ? LogStore.create('messages.log', storage) : storage, + config: config }; setInterval(function () { Object.keys(ctx.users).forEach(function (userId) { @@ -183,7 +215,7 @@ let run = module.exports.run = function (storage, socketServer) { ctx.users[user.id] = user; sendMsg(ctx, user, [0, '', 'IDENT', user.id]); socket.on('message', function(message) { - if (LOG_MESSAGES) { console.log('>'+message); } + if (ctx.config.logToStdout) { console.log('>'+message); } try { handleMessage(ctx, user, message); } catch (e) { diff --git a/config.js.dist b/config.js.dist index b79777eb7..4b5702574 100644 --- a/config.js.dist +++ b/config.js.dist @@ -12,6 +12,20 @@ module.exports = { // the port used for websockets websocketPort: 3000, + /* Cryptpad can log activity to stdout + * This may be useful for debugging + */ + logToStdout: false, + + /* Cryptpad can be configured to remove channels some number of ms + after the last remaining client has disconnected. + + Default behaviour is to keep channels forever. + If you enable channel removal, the default removal time is one minute + */ + removeChannels: false, + channelRemovalTimeout: 60000, + // You now have a choice of storage engines /* amnesiadb only exists in memory. diff --git a/server.js b/server.js index 819480fcf..0b963f730 100644 --- a/server.js +++ b/server.js @@ -81,6 +81,6 @@ if (config.websocketPort !== config.httpPort) { var wsSrv = new WebSocketServer(wsConfig); Storage.create(config, function (store) { console.log('DB connected'); - NetfluxSrv.run(store, wsSrv); + NetfluxSrv.run(store, wsSrv, config); WebRTCSrv.run(wsSrv); }); diff --git a/storage/LogStore.js b/storage/LogStore.js index d588c82f5..14761e6b7 100644 --- a/storage/LogStore.js +++ b/storage/LogStore.js @@ -8,11 +8,12 @@ var create = module.exports.create = function(filePath, backingStore) { var file = Fs.createWriteStream(filePath, {flags: 'a+'}); - return { - message: function(channel, msg, callback) { - message(file, msg); - backingStore.message(channel, msg, callback); - }, - getMessages: backingStore.getMessages + var originalMessageFunction = backingStore.message; + + backingStore.message = function(channel, msg, callback) { + message(file, msg); + originalMessageFunction(channel, msg, callback); }; + + return backingStore; }; diff --git a/storage/README.md b/storage/README.md index 3c854d00c..7f406cf94 100644 --- a/storage/README.md +++ b/storage/README.md @@ -42,6 +42,12 @@ This function accepts the name of the channel in which the user is interested, t It is only implemented within the leveldb adaptor, making our latest code incompatible with the other back ends. While we migrate to our new Netflux API, only the leveldb adaptor will be supported. +## removeChannel(channelName, callback) + +This method is called (optionally, see config.js.dist for more info) some amount of time after the last client in a channel disconnects. + +It should remove any history of that channel, and execute a callback which takes an error message as an argument. + ## Documenting your adaptor Naturally, you should comment your code well before making a PR. diff --git a/storage/amnesia.js b/storage/amnesia.js index bd414b8fb..b7c1702e6 100644 --- a/storage/amnesia.js +++ b/storage/amnesia.js @@ -19,6 +19,10 @@ module.exports.create = function(conf, cb){ var db=[], index=0; + if (conf.removeChannels) { + console.log("Server is set to remove channels %sms after the last remaining client leaves.", conf.channelRemovalTimeout); + } + cb({ message: function(channelName, content, cb){ var val = { @@ -41,5 +45,12 @@ module.exports.create = function(conf, cb){ }); if (cb) { cb(); } }, + removeChannel: function (channelName, cb) { + var err = false; + db = db.filter(function (msg) { + return msg.chan !== channelName; + }); + cb(err); + }, }); };