cryptpad/www/debug/inner.js
2020-06-26 15:58:03 +02:00

702 lines
27 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/common/toolbar.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<old.length; i++) {
try {
pad = old[i];
href = (pad.href && pad.href.indexOf('#') !== -1) ? pad.href : pad.roHref;
chan = channelByHref[href];
if (!chan && href) {
parsed = Hash.parsePadUrl(href);
chan = parsed.hashData && Util.base64ToHex(parsed.hashData.channel || '');
channelByHref[href] = chan;
}
if (chan && (!pads[chan] || pads[chan].atime < pad.atime)) {
pads[chan] = {
atime: +new Date(pad.atime),
href: href,
title: pad.title,
owned: isOwned(pad),
expired: pad.expire && pad.expire < (+new Date())
};
}
} catch (e) {}
}
}
// Get pads from the new storage key
if (ids) {
for (var id in ids) {
try {
pad = ids[id];
href = (pad.href && pad.href.indexOf('#') !== -1) ? pad.href : pad.roHref;
chan = pad.channel || channelByHref[href];
if (!chan) {
if (href) {
parsed = Hash.parsePadUrl(href);
chan = (parsed.hashData && Util.base64ToHex(parsed.hashData.channel || '')) ||
(Hash.getSecrets(parsed.type, parsed.hash, pad.password) || {}).channel;
channelByHref[href] = chan;
}
}
if (chan && (!pads[chan] || pads[chan].atime < pad.atime)) {
pads[chan] = {
atime: +new Date(pad.atime),
href: href,
title: pad.title,
owned: isOwned(pad),
expired: pad.expire && pad.expire < (+new Date())
};
}
} catch (e) {}
}
}
return c;
};
var allChannels;
var deleted;
nThen(function (W) {
var nt = nThen;
// Safely get all the pads from all the states
var i = 0;
var next = function (block, doc) {
nt = nt(W(function (waitFor) {
i++;
var doc2 = parseBlock(block, doc);
progress(Math.min(i/length, 1));
var c = block.getChildren();
setTimeout(waitFor(), 1);
c.forEach(function (b) {
next(b, doc2);
});
})).nThen;
};
var root = chainpad.getRootBlock();
next(root);
}).nThen(function (waitFor) {
// Make the table
allChannels = Object.keys(pads);
sframeChan.query('Q_DRIVE_GETDELETED', {list:allChannels}, waitFor(function (err, data) {
deleted = data;
}));
}).nThen(function () {
// Current status
try {
var parsed = JSON.parse(chainpad.getUserDoc());
var drive = parsed.metadata ? parsed : parsed.drive;
var channels = Object.keys(drive[Constants.storageKey] || {}).map(function (id) {
return drive[Constants.storageKey][id].channel;
});
} catch (e) {
console.error(e);
}
// Header
var rows = [h('tr', [// TODO
h('th', '#'),
h('th', 'Title'),
h('th', 'URL'),
h('th', 'Last visited'),
h('th', 'Owned'),
h('th', 'CryptDrive status'),
h('th', 'Server status'),
])];
// Body
var body = allChannels;
body.sort(function (a, b) {
return pads[a].atime - pads[b].atime;
});
body.forEach(function (id, i) {
var p = pads[id];
var del = deleted.indexOf(id) !== -1;
var removed = channels.indexOf(id) === -1;
rows.push(h('tr', [
h('td', String(i+1)),
h('td', {
title: p.title
}, p.title),
h('td', h('a', {
href: origin+p.href,
target: '_blank'
}, p.href)),
h('td', new Date(p.atime).toLocaleString()),
h('td', p.owned ? 'Yes' : 'No'),
h('td'+(p.expired || removed ?'.cp-debug-nok':'.cp-debug-ok'),
p.expired ? 'Expired' :
(!removed ? 'Stored' : 'Deleted')),// TODO
h('td'+(del?'.cp-debug-nok':'.cp-debug-ok'), del ? 'Missing' : 'Available'),// TODO
]));
});
// Table
var t = h('table', rows);
cb(t);
});
};
var getGraph = function (chainpad, cb) {
var hashes = metadataMgr.getPrivateData().hashes;
var hash = hashes.editHash || hashes.viewHash;
var chan = Hash.hrefToHexChannelId('/drive/#'+hash);
var makeGraph = function () {
var out = [
chan + ' digraph {'
];
var parseBlock = function (x) {
var c = x.getChildren();
var label = x.hashOf.slice(0,8) + ' (' + x.parentCount + ' - ' + x.recvOrder + ')';
var p = x.getParent();
if (p && p.getChildren().length === 1 && c.length === 1) {
label = '...';
var gc = c;
while (gc.length === 1) {
c = gc;
gc = c[0].getChildren();
}
}
var nodeInfo = [' p' + x.hashOf + '[label="' + label + '"'];
if (x.isCheckpoint && label !== '...') { nodeInfo.push(',color=red,weight=0.5'); }
nodeInfo.push(']');
out.push(nodeInfo.join(''));
c.forEach(function (child) {
out.push(' p' + x.hashOf + ' -> 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,
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 start = 0;
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 i = 0;
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();
$('#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: ['pad'],
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.$drawer.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.$drawer.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');
});
});
});