define([ 'jquery', '/bower_components/chainpad-crypto/crypto.js', '/common/toolbar3.js', 'json.sortify', '/common/common-util.js', '/bower_components/nthen/index.js', '/common/sframe-common.js', '/common/common-interface.js', '/common/common-hash.js', '/common/common-constants.js', '/common/hyperscript.js', '/api/config', '/common/common-realtime.js', '/customize/messages.js', '/customize/application_config.js', '/debug/chainpad.dist.js', '/bower_components/secure-fabric.js/dist/fabric.min.js', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/debug/app-debug.less', ], function ( $, Crypto, Toolbar, JSONSortify, Util, nThen, SFCommon, UI, Hash, Constants, h, ApiConfig, CommonRealtime, Messages, AppConfig, ChainWalk) { var APP = window.APP = { $: $, AppConfig: AppConfig, SFCommon: SFCommon, Crypto: Crypto, ApiConfig: ApiConfig }; var toolbar; var common; nThen(function (waitFor) { $(waitFor(function () { UI.addLoadingScreen(); })); SFCommon.create(waitFor(function (c) { APP.common = common = c; })); }).nThen(function (/*waitFor*/) { var initializing = true; var $bar = $('#cp-toolbar'); var Title; var cpNfInner; var metadataMgr; var readOnly = true; var sframeChan = common.getSframeChannel(); var getHrefsTable = function (chainpad, length, cb, progress) { var priv = metadataMgr.getPrivateData(); var origin = priv.origin; var edPublic = priv.edPublic; var pads = {}; var channelByHref = {}; var isOwned = function (data) { data = data || {}; return data && data.owners && Array.isArray(data.owners) && data.owners.indexOf(edPublic) !== -1; }; var parseBlock = function (block, doc) { var c = block.getContent(doc).doc; if (!c) { return void console.error(block); } var p; try { p = JSON.parse(c); if (!p.metadata) { p = p.drive || {}; } } catch (e) { console.error(e); p = {}; } // Get pads from the old storage key var old = p[Constants.oldStorageKey]; var ids = p[Constants.storageKey]; var pad, parsed, chan, href; if (old && Array.isArray(old)) { for (var i = 0; i p' + child.hashOf); parseBlock(child); }); }; parseBlock(chainpad.getRootBlock()); out.push('}'); return out.join('\n'); }; cb(makeGraph()); }; var getFullChainpad = function (history, length, cb, progress) { var chainpad = ChainWalk.create({ userName: 'debug', initialState: '', logLevel: 0, noPrune: true }); var nt = nThen; history.forEach(function (msg, i) { nt = nt(function (waitFor) { chainpad.message(msg); progress(Math.min(i/length, 1)); setTimeout(waitFor()); }).nThen; }); nt(function () { cb(chainpad); }); }; var fullHistoryCalled = false; var getFullHistory = function () { var priv = metadataMgr.getPrivateData(); if (fullHistoryCalled) { return; } fullHistoryCalled = true; // Set spinner var content = h('div#cp-app-debug-loading', [ h('h2', 'Step 1/3'), 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); // Update progress bar var decrypting = false; var length = 0; var decryptProgress = h('span', '0%'); sframeChan.on('EV_FULL_HISTORY_STATUS', function (progress) { if (!decrypting) { // Add the progress bar the first time decrypting = true; var content = h('div.cp-app-debug-progress.cp-loading-progress', [ h('h2', 'Step 2/3'), h('p', 'Decrypting your history...'), h('span.fa.fa-circle-o-notch.fa-spin.fa-3x.fa-fw'), h('br'), decryptProgress ]); $('#cp-app-debug-content').html('').append(content); } length++; decryptProgress.innerHTML = (progress*100).toFixed(2) + '%'; }); // Get full history sframeChan.query('Q_GET_FULL_HISTORY', null, function (err, data) { // History is ready. // Display the graph code, and if the doc is a drive, display the button to list all the pads // Graph var graph = h('div.cp-app-debug-content-graph'); var seeAllButton = h('button.btn.btn-success', 'Get the list'); var hrefs = h('div.cp-app-debug-content-hrefs', [ h('h2', 'List all the pads ever stored in your CryptDrive'), // TODO ]); var parseProgress = h('span', '0%'); var content = h('div#cp-app-debug-loading', [ h('h2', 'Step 3/3'), h('p', 'Parsing history...'),// TODO h('span.fa.fa-circle-o-notch.fa-spin.fa-3x.fa-fw'), h('br'), parseProgress ]); $('#cp-app-debug-content').html('').append(content); getFullChainpad(data, length, function (chainpad) { var content = h('div.cp-app-debug-content', [ graph, priv.debugDrive ? hrefs : '' ]); $('#cp-app-debug-content').html('').append(content); // Table if (priv.debugDrive) { var clicked = false; $(seeAllButton).click(function () { if (clicked) { return; } clicked = true; $(seeAllButton).remove(); // Make the table var progress = h('span', '0%'); var loading = h('div', [ 'Loading data...', h('br'), progress ]); hrefs.append(loading); getHrefsTable(chainpad, length, function (table) { loading.innerHTML = ''; hrefs.append(table); }, function (p) { progress.innerHTML = (p*100).toFixed(2) + '%'; }); }).appendTo(hrefs); } // Graph var code = h('code'); getGraph(chainpad, function (graphVal) { code.innerHTML = graphVal; $(graph).append(h('h2', 'Graph')); // TODO $(graph).append(code); }); }, function (p) { parseProgress.innerHTML = (p*100).toFixed(2) + '%'; }); }, {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, 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'), input = h('input', {type: 'number'}), right = h('span.fa.fa-chevron-right'), ]), h('br'), replay = h('pre.cp-debug-replay'), ]); var $input = $(input); var $left = $(left); var $right = $(right); $('#cp-app-debug-content').html('').append(content); var chainpad = makeChainpad(); console.warn(chainpad); var i = 0; var messages = data.slice(); var play = function (_i) { if (_i < 1) { _i = 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; $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); } } $(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 (e.which === 37 || e.which === 40) { // Left or down e.preventDefault(); return; } if (e.which === 38 || e.which === 39) { // Up or right e.preventDefault(); return; } }); $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)); }); }, {timeout: 2147483647}); // Max 32-bit integer }; var getContent = function () { if ($('#cp-app-debug-content').is(':visible')) { $('#cp-app-debug-content').hide(); $('#cp-app-debug-history').show(); $('#cp-app-debug-get-content').removeClass('cp-toolbar-button-active'); return; } $('#cp-app-debug-content').css('display', 'flex'); $('#cp-app-debug-history').hide(); $('#cp-app-debug-get-content').addClass('cp-toolbar-button-active'); }; 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, buttonReplay ]); $('#cp-app-debug-content').html('').append(content); }; setInitContent(); var config = APP.config = { readOnly: readOnly, // cryptpad debug logging (default is 1) // logLevel: 0, validateContent: function (content) { try { JSON.parse(content); return true; } catch (e) { console.log("Failed to parse, rejecting patch"); return false; } } }; var history = false; var setHistory = function (bool, update) { history = bool; if (!bool && update) { config.onRemote(); } }; var displayDoc = function (doc) { $('#cp-app-debug-history').text(JSON.stringify(doc, 0, 2)); console.log(doc); }; var toRestore; config.onLocal = function (a, restore) { if (!toRestore || !restore) { return; } cpNfInner.chainpad.contentUpdate(toRestore); }; config.onInit = function (info) { Title = common.createTitle({}); var configTb = { displayed: ['title', 'useradmin', 'spinner', 'share', 'userlist', 'newpad', 'limit'], title: Title.getTitleConfig(), metadataMgr: metadataMgr, readOnly: 1, realtime: info.realtime, sfCommon: common, $container: $bar, $contentContainer: $('#cp-app-debug') }; toolbar = APP.toolbar = Toolbar.create(configTb); Title.setToolbar(toolbar); /* add a history button */ var histConfig = { onLocal: function () { config.onLocal(null, true); }, onRemote: config.onRemote, setHistory: setHistory, applyVal: function (val) { toRestore = val; displayDoc(JSON.parse(val) || {}); }, $toolbar: $bar, debug: true }; var $hist = common.createButton('history', true, {histConfig: histConfig}); $hist.addClass('cp-hidden-if-readonly'); toolbar.$rightside.append($hist); var $content = common.createButton(null, true, { icon: 'fa-question', title: 'Get debugging graph', // TODO name: 'graph', id: 'cp-app-debug-get-content' }); $content.click(getContent); toolbar.$rightside.append($content); }; config.onReady = function (info) { if (APP.realtime !== info.realtime) { APP.realtime = info.realtime; } var userDoc = APP.realtime.getUserDoc(); if (userDoc !== "") { var hjson = JSON.parse(userDoc); if (Array.isArray(hjson)) { metadataMgr.updateMetadata(hjson[3]); } else if (hjson && hjson.metadata) { metadataMgr.updateMetadata(hjson.metadata); } displayDoc(hjson); } metadataMgr.updateTitle(''); initializing = false; $('#cp-app-debug-history').show(); UI.removeLoadingScreen(); }; config.onRemote = function () { if (initializing) { return; } if (history) { return; } var userDoc = APP.realtime.getUserDoc(); var json = JSON.parse(userDoc); if (Array.isArray(json)) { metadataMgr.updateMetadata(json[3]); } else if (json && json.metadata) { metadataMgr.updateMetadata(json.metadata); } displayDoc(json); }; config.onAbort = function () { console.log('onAbort'); }; config.onConnectionChange = function (info) { console.log('onConnectionChange', info.state); }; cpNfInner = APP.cpNfInner = common.startRealtime(config); metadataMgr = APP.metadataMgr = cpNfInner.metadataMgr; cpNfInner.onInfiniteSpinner(function () { console.error('infinite spinner'); }); }); });