diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index da7f043f3..604c799f3 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1884,7 +1884,15 @@ define([ if (msg) { msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, ''); //var decryptedMsg = crypto.decrypt(msg, true); - msgs.push(msg); + if (data.debug) { + msgs.push({ + msg: msg, + author: parsed[1][1], + time: parsed[1][5] + }); + } else { + msgs.push(msg); + } } }; network.on('message', onMsg); diff --git a/www/common/sframe-common-history.js b/www/common/sframe-common-history.js index be2793128..0217fee7a 100644 --- a/www/common/sframe-common-history.js +++ b/www/common/sframe-common-history.js @@ -71,7 +71,10 @@ define([ lastKnownHash = data.lastKnownHash; isComplete = data.isFull; var messages = (data.messages || []).map(function (obj) { - return obj.msg; + if (!config.debug) { + return obj.msg; + } + return obj; }); if (config.debug) { console.log(data.messages); } Array.prototype.unshift.apply(allMessages, messages); // Destructive concat diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index f4f42707a..b781ac95e 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -835,17 +835,26 @@ define([ sframeChan.on('Q_GET_FULL_HISTORY', function (data, cb) { var crypto = Crypto.createEncryptor(secret.keys); Cryptpad.getFullHistory({ + debug: data && data.debug, channel: secret.channel, validateKey: secret.keys.validateKey }, function (encryptedMsgs) { var nt = nThen; var decryptedMsgs = []; var total = encryptedMsgs.length; - encryptedMsgs.forEach(function (msg, i) { + encryptedMsgs.forEach(function (_msg, i) { nt = nt(function (waitFor) { // The 3rd parameter "true" means we're going to skip signature validation. // We don't need it since the message is already validated serverside by hk - decryptedMsgs.push(crypto.decrypt(msg, true, true)); + if (typeof(_msg) === "object") { + decryptedMsgs.push({ + author: _msg.author, + time: _msg.time, + msg: crypto.decrypt(_msg.msg, true, true) + }); + } else { + decryptedMsgs.push(crypto.decrypt(_msg, true, true)); + } setTimeout(waitFor(function () { sframeChan.event('EV_FULL_HISTORY_STATUS', (i+1)/total); })); diff --git a/www/debug/app-debug.less b/www/debug/app-debug.less index 52598cec8..697a46612 100644 --- a/www/debug/app-debug.less +++ b/www/debug/app-debug.less @@ -22,13 +22,14 @@ display: none; } #cp-app-debug-content { - margin: 50px; + margin: 10px 50px; flex-flow: column; align-items: center; justify-content: center; .cp-app-debug-content { flex: 1; min-height: 0; + justify-content: center; } .cp-app-debug-progress, .cp-app-debug-init { text-align: center; @@ -57,6 +58,27 @@ background-color: rgba(0,0,0,0.1); } } + .fa-chevron-left, .fa-chevron-right { + margin: 5px 20px; + cursor: pointer; + &:hover { + color: #777; + } + } + .cp-app-debug-progress { + display: flex; + flex: 1; + min-height: 0; + flex-flow: column; + } + pre.cp-debug-replay { + text-align: left; + white-space: pre-wrap; + word-break: break-word; + overflow: auto; + flex: 1; + min-height: 0; + } } } diff --git a/www/debug/inner.js b/www/debug/inner.js index 3a98b883e..83c7c29f0 100644 --- a/www/debug/inner.js +++ b/www/debug/inner.js @@ -389,6 +389,159 @@ define([ }, {timeout: 2147483647}); // Max 32-bit integer }; + var replayFullHistory = function () { + // Set spinner + var content = h('div#cp-app-debug-loading', [ + h('p', 'Loading history from the server...'), + h('span.fa.fa-circle-o-notch.fa-spin.fa-3x.fa-fw') + ]); + $('#cp-app-debug-content').html('').append(content); + var makeChainpad = function () { + return window.ChainPad.create({ + userName: 'debug', + initialState: '', + logLevel: 2, + noPrune: true, + validateContent: function (content) { + try { + JSON.parse(content); + return true; + } catch (e) { + console.log('Failed to parse, rejecting patch'); + return false; + } + }, + }); + }; + sframeChan.query('Q_GET_FULL_HISTORY', { + debug: true, + }, function (err, data) { + var replay, input, left, right; + var content = h('div.cp-app-debug-progress.cp-loading-progress', [ + h('p', [ + left = h('span.fa.fa-chevron-left'), + h('label', 'Start'), + start = h('input', {type: 'number', value: 0}), + h('label', 'State'), + input = h('input', {type: 'number', min: 1}), + right = h('span.fa.fa-chevron-right'), + ]), + h('br'), + replay = h('pre.cp-debug-replay'), + ]); + var $start = $(start); + var $input = $(input); + var $left = $(left); + var $right = $(right); + + $('#cp-app-debug-content').html('').append(content); + var chainpad = makeChainpad(); + console.warn(chainpad); + + var start = 0; + var i = 0; + var messages = data.slice(); + var play = function (_i) { + if (_i < (start+1)) { _i = start + 1; } + if (_i > data.length - 1) { _i = data.length - 1; } + if (_i < i) { + chainpad.abort(); + chainpad = makeChainpad(); + console.warn(chainpad); + i = 0; + } + var messages = data.slice(i, _i); + i = _i; + $start.val(start); + $input.val(i); + messages.forEach(function (obj) { + chainpad.message(obj); + }); + if (messages.length) { + var hashes = Object.keys(chainpad._.messages); + var currentHash = hashes[hashes.length - 1]; + var best = chainpad.getAuthBlock(); + var current = chainpad.getBlockForHash(currentHash); + if (best.hashOf === currentHash) { + console.log("Best", best); + } else { + console.warn("Current", current); + console.log("Best", best); + } + } + if (!chainpad.getUserDoc()) { + $(replay).text(''); + return; + } + $(replay).text(JSON.stringify(JSON.parse(chainpad.getUserDoc()), 0, 2)); + }; + play(1); + $left.click(function () { + play(i-1); + }); + $right.click(function () { + play(i+1); + }); + + $input.keydown(function (e) { + if ([37, 38, 39, 40].indexOf(e.which) !== -1) { + e.preventDefault(); + } + }); + $input.keyup(function (e) { + var val = Number($input.val()); + if (e.which === 37 || e.which === 40) { // Left or down + e.preventDefault(); + play(val - 1); + return; + } + if (e.which === 38 || e.which === 39) { // Up or right + e.preventDefault(); + play(val + 1); + return; + } + if (e.which !== 13) { return; } + if (!val) { + $input.val(1); + return; + } + play(Number(val)); + }); + + // Initial state + $start.keydown(function (e) { + if ([37, 38, 39, 40].indexOf(e.which) !== -1) { + e.preventDefault(); + } + }); + $start.keyup(function (e) { + var val = Number($start.val()); + e.preventDefault(); + if ([37, 38, 39, 40, 13].indexOf(e.which) !== -1) { + chainpad.abort(); + chainpad = makeChainpad(); + } + if (e.which === 37 || e.which === 40) { // Left or down + start = Math.max(0, val - 1); + i = start; + play(i); + return; + } + if (e.which === 38 || e.which === 39) { // Up or right + start = Math.min(data.length - 1, val + 1); + i = start; + play(i); + return; + } + if (e.which !== 13) { return; } + start = Number(val); + if (!val) { start = 0; } + i = start; + play(i); + }); + }, {timeout: 2147483647}); // Max 32-bit integer + }; + var getContent = function () { if ($('#cp-app-debug-content').is(':visible')) { $('#cp-app-debug-content').hide(); @@ -402,11 +555,14 @@ define([ }; var setInitContent = function () { var button = h('button.btn.btn-success', 'Load history'); + var buttonReplay = h('button.btn.btn-success', 'Replay'); $(button).click(getFullHistory); + $(buttonReplay).click(replayFullHistory); var content = h('p.cp-app-debug-init', [ 'To get better debugging tools, we need to load the entire history of the document. This make take some time.', // TODO h('br'), - button + button, + buttonReplay ]); $('#cp-app-debug-content').html('').append(content); };