From fe261c34bbc42011a4cf89c9abba8b99509ca4b6 Mon Sep 17 00:00:00 2001 From: ansuz Date: Sat, 14 May 2016 12:47:59 +0200 Subject: [PATCH 1/9] set log level via the config file, not via a constant --- NetfluxWebsocketSrv.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/NetfluxWebsocketSrv.js b/NetfluxWebsocketSrv.js index 13ec51cca..6fad102ed 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); @@ -153,11 +153,12 @@ const handleMessage = function (ctx, user, msg) { } }; -let run = module.exports.run = function (storage, socketServer) { +let run = module.exports.run = function (storage, socketServer, config) { let ctx = { users: {}, channels: {}, - store: (USE_FILE_BACKUP_STORAGE) ? LogStore.create('messages.log', storage) : storage + store: (USE_FILE_BACKUP_STORAGE) ? LogStore.create('messages.log', storage) : storage, + config: config }; setInterval(function () { Object.keys(ctx.users).forEach(function (userId) { @@ -183,7 +184,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) { From a08efc568ba676b32a20ab03fce252fa5cb35cd6 Mon Sep 17 00:00:00 2001 From: ansuz Date: Sat, 14 May 2016 12:50:03 +0200 Subject: [PATCH 2/9] update default config to contain logToStdout --- config.js.dist | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.js.dist b/config.js.dist index b79777eb7..9d0ef5f32 100644 --- a/config.js.dist +++ b/config.js.dist @@ -12,6 +12,11 @@ module.exports = { // the port used for websockets websocketPort: 3000, + /* Cryptpad can log activity to stdout + * This may be useful for debugging + */ + logToStdout: false, + // You now have a choice of storage engines /* amnesiadb only exists in memory. From 061cffe462c2b14b4e2991b1a9178e3fce53f1d4 Mon Sep 17 00:00:00 2001 From: ansuz Date: Sat, 14 May 2016 12:51:52 +0200 Subject: [PATCH 3/9] transform and pass through the existing store --- storage/LogStore.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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; }; From d59824929d71d360f3281db174d8a941133af02f Mon Sep 17 00:00:00 2001 From: ansuz Date: Sat, 14 May 2016 12:52:34 +0200 Subject: [PATCH 4/9] pass config object into NetfluxServer --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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); }); From 3232671a6a24361d34bef0d9fa650dfca5e39bda Mon Sep 17 00:00:00 2001 From: ansuz Date: Sat, 14 May 2016 13:04:05 +0200 Subject: [PATCH 5/9] implement optional channel removal hooks add configuration variables to the default config --- NetfluxWebsocketSrv.js | 24 ++++++++++++++++++++++++ config.js.dist | 9 +++++++++ 2 files changed, 33 insertions(+) diff --git a/NetfluxWebsocketSrv.js b/NetfluxWebsocketSrv.js index 6fad102ed..48444f7ef 100644 --- a/NetfluxWebsocketSrv.js +++ b/NetfluxWebsocketSrv.js @@ -62,6 +62,17 @@ 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 && typeof(ctx.store.removeChannel) === 'function') { + ctx.timeouts[chanName] = setTimeout(function () { + ctx.store.removeChannel(chanName, function (err) { + if (err) { console.error("[removeChannelErr]: %s", err); } + console.log("Deleted channel [%s] history from database...", chanName); + }); + }, ctx.config.channelRemovalTimeout); + } } else { sendChannelMessage(ctx, chan, [user.id, 'LEAVE', chanName, 'Quit: [ dropUser() ]']); } @@ -91,6 +102,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]); @@ -154,9 +171,16 @@ const handleMessage = function (ctx, user, msg) { }; 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: {}, + timeouts: {}, store: (USE_FILE_BACKUP_STORAGE) ? LogStore.create('messages.log', storage) : storage, config: config }; diff --git a/config.js.dist b/config.js.dist index 9d0ef5f32..4b5702574 100644 --- a/config.js.dist +++ b/config.js.dist @@ -17,6 +17,15 @@ module.exports = { */ 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. From 1b2c129380938e89881927cc7815cf6df963e740 Mon Sep 17 00:00:00 2001 From: ansuz Date: Sat, 14 May 2016 13:05:21 +0200 Subject: [PATCH 6/9] implement channel removal in amnesiadb --- storage/amnesia.js | 11 +++++++++++ 1 file changed, 11 insertions(+) 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); + }, }); }; From 2d3ed84f5d7505f70614af32d9a07a739aba1fbf Mon Sep 17 00:00:00 2001 From: ansuz Date: Sat, 14 May 2016 13:14:55 +0200 Subject: [PATCH 7/9] don't say you deleted database entries if you didn't --- NetfluxWebsocketSrv.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/NetfluxWebsocketSrv.js b/NetfluxWebsocketSrv.js index 48444f7ef..3d0d44d7f 100644 --- a/NetfluxWebsocketSrv.js +++ b/NetfluxWebsocketSrv.js @@ -69,7 +69,9 @@ dropUser = function (ctx, user) { ctx.timeouts[chanName] = setTimeout(function () { ctx.store.removeChannel(chanName, function (err) { if (err) { console.error("[removeChannelErr]: %s", err); } - console.log("Deleted channel [%s] history from database...", chanName); + else { + console.log("Deleted channel [%s] history from database...", chanName); + } }); }, ctx.config.channelRemovalTimeout); } From 62dba6de7ba6a8616e666ff1182f7656430df1d4 Mon Sep 17 00:00:00 2001 From: ansuz Date: Sat, 14 May 2016 13:27:15 +0200 Subject: [PATCH 8/9] add specific complaints for removeChannel's non-existence --- NetfluxWebsocketSrv.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/NetfluxWebsocketSrv.js b/NetfluxWebsocketSrv.js index 3d0d44d7f..8ce1ab442 100644 --- a/NetfluxWebsocketSrv.js +++ b/NetfluxWebsocketSrv.js @@ -65,15 +65,20 @@ dropUser = function (ctx, user) { /* Call removeChannel if it is a function and channel removal is set to true in the config file */ - if (ctx.config.removeChannels && 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); + 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() ]']); From 9c15a43268c09b7b578b36e117c219aa2fd332a4 Mon Sep 17 00:00:00 2001 From: ansuz Date: Sat, 14 May 2016 13:50:25 +0200 Subject: [PATCH 9/9] add removeChannel documentation to storage/README --- storage/README.md | 6 ++++++ 1 file changed, 6 insertions(+) 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.