cryptpad/www/debug/inner.js

703 lines
27 KiB
JavaScript
Raw Normal View History

2017-11-21 16:37:38 +00:00
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
2020-05-07 09:58:58 +00:00
'/common/toolbar.js',
2017-11-21 16:37:38 +00:00
'json.sortify',
'/common/common-util.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
'/common/common-interface.js',
2019-02-19 14:04:19 +00:00
'/common/common-hash.js',
2019-02-25 17:43:32 +00:00
'/common/common-constants.js',
2019-02-19 13:31:05 +00:00
'/common/hyperscript.js',
2017-11-21 16:37:38 +00:00
'/api/config',
'/common/common-realtime.js',
'/customize/messages.js',
'/customize/application_config.js',
2019-02-19 13:31:05 +00:00
'/debug/chainpad.dist.js',
2017-11-21 16:37:38 +00:00
'/bower_components/secure-fabric.js/dist/fabric.min.js',
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
2018-03-21 17:31:53 +00:00
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
2018-07-14 13:15:23 +00:00
'less!/debug/app-debug.less',
2017-11-21 16:37:38 +00:00
], function (
$,
Crypto,
Toolbar,
JSONSortify,
Util,
nThen,
SFCommon,
UI,
2019-02-19 14:04:19 +00:00
Hash,
2019-02-25 17:43:32 +00:00
Constants,
2019-02-19 13:31:05 +00:00
h,
2017-11-21 16:37:38 +00:00
ApiConfig,
CommonRealtime,
Messages,
2019-02-19 13:31:05 +00:00
AppConfig,
ChainWalk)
2017-11-21 16:37:38 +00:00
{
var APP = window.APP = {
$: $,
AppConfig: AppConfig,
SFCommon: SFCommon,
2017-11-21 16:42:50 +00:00
Crypto: Crypto,
ApiConfig: ApiConfig
2017-11-21 16:37:38 +00:00
};
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;
2019-02-19 13:31:05 +00:00
var sframeChan = common.getSframeChannel();
2019-02-25 17:43:32 +00:00
var getHrefsTable = function (chainpad, length, cb, progress) {
var priv = metadataMgr.getPrivateData();
var origin = priv.origin;
2019-02-25 17:43:32 +00:00
var edPublic = priv.edPublic;
var pads = {};
var channelByHref = {};
2019-02-25 17:43:32 +00:00
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;
2019-02-25 17:43:32 +00:00
if (!c) { return void console.error(block); }
var p;
try {
p = JSON.parse(c);
if (!p.metadata) {
p = p.drive || {};
}
2019-02-25 17:43:32 +00:00
} 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];
2019-10-07 16:30:46 +00:00
href = (pad.href && pad.href.indexOf('#') !== -1) ? pad.href : pad.roHref;
chan = channelByHref[href];
if (!chan && href) {
2019-02-25 17:43:32 +00:00
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())
};
2019-02-25 17:43:32 +00:00
}
} catch (e) {}
}
}
// Get pads from the new storage key
if (ids) {
for (var id in ids) {
try {
pad = ids[id];
2019-10-07 16:30:46 +00:00
href = (pad.href && pad.href.indexOf('#') !== -1) ? pad.href : pad.roHref;
chan = pad.channel || channelByHref[href];
2019-02-25 17:43:32 +00:00
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;
2019-02-25 17:43:32 +00:00
}
}
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;
2019-02-25 17:43:32 +00:00
};
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
2019-02-25 17:43:32 +00:00
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;
2019-02-25 17:43:32 +00:00
});
} catch (e) {
console.error(e);
}
// Header
var rows = [h('tr', [// TODO
2019-02-25 17:43:32 +00:00
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;
2019-02-25 17:43:32 +00:00
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)),
2019-02-25 17:43:32 +00:00
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
2019-02-25 17:43:32 +00:00
]));
});
// Table
var t = h('table', rows);
cb(t);
2019-02-19 13:31:05 +00:00
});
2019-02-25 17:43:32 +00:00
};
var getGraph = function (chainpad, cb) {
2019-05-29 17:00:20 +00:00
var hashes = metadataMgr.getPrivateData().hashes;
2019-02-19 14:04:19 +00:00
var hash = hashes.editHash || hashes.viewHash;
var chan = Hash.hrefToHexChannelId('/drive/#'+hash);
2019-02-19 13:31:05 +00:00
var makeGraph = function () {
var out = [
2019-02-19 14:04:19 +00:00
chan + ' digraph {'
2019-02-19 13:31:05 +00:00
];
var parseBlock = function (x) {
2019-02-19 13:38:13 +00:00
var c = x.getChildren();
var label = x.hashOf.slice(0,8) + ' (' + x.parentCount + ' - ' + x.recvOrder + ')';
2019-02-19 13:31:05 +00:00
var p = x.getParent();
if (p && p.getChildren().length === 1 && c.length === 1) {
label = '...';
2019-02-19 13:38:13 +00:00
var gc = c;
2019-02-19 13:31:05 +00:00
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');
};
2019-02-25 17:43:32 +00:00
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());
2019-02-25 17:43:32 +00:00
}).nThen;
});
nt(function () {
cb(chainpad);
2019-02-26 16:42:21 +00:00
});
2019-02-25 17:43:32 +00:00
};
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'),
2019-02-25 17:43:32 +00:00
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%');
2019-02-25 17:43:32 +00:00
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'),
2019-02-25 17:43:32 +00:00
h('p', 'Decrypting your history...'),
h('span.fa.fa-circle-o-notch.fa-spin.fa-3x.fa-fw'),
h('br'),
decryptProgress
2019-02-25 17:43:32 +00:00
]);
$('#cp-app-debug-content').html('').append(content);
}
length++;
2019-02-26 16:42:21 +00:00
decryptProgress.innerHTML = (progress*100).toFixed(2) + '%';
2019-02-25 17:43:32 +00:00
});
// Get full history
2019-02-19 13:31:05 +00:00
sframeChan.query('Q_GET_FULL_HISTORY', null, function (err, data) {
2019-02-25 17:43:32 +00:00
// 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');
2019-02-25 17:43:32 +00:00
var hrefs = h('div.cp-app-debug-content-hrefs', [
h('h2', 'List all the pads ever stored in your CryptDrive'), // TODO
2019-02-25 17:43:32 +00:00
]);
var parseProgress = h('span', '0%');
var content = h('div#cp-app-debug-loading', [
h('h2', 'Step 3/3'),
h('p', 'Parsing history...'),// TODO
2019-02-25 17:43:32 +00:00
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
2019-02-25 17:43:32 +00:00
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) {
2019-02-26 16:42:21 +00:00
progress.innerHTML = (p*100).toFixed(2) + '%';
2019-02-25 17:43:32 +00:00
});
}).appendTo(hrefs);
}
// Graph
var code = h('code');
getGraph(chainpad, function (graphVal) {
code.innerHTML = graphVal;
$(graph).append(h('h2', 'Graph')); // TODO
2019-02-25 17:43:32 +00:00
$(graph).append(code);
});
}, function (p) {
2019-02-26 16:42:21 +00:00
parseProgress.innerHTML = (p*100).toFixed(2) + '%';
2019-02-19 13:31:05 +00:00
});
2019-02-25 17:43:32 +00:00
}, {timeout: 2147483647}); // Max 32-bit integer
2019-02-19 13:31:05 +00:00
};
2017-11-21 16:37:38 +00:00
2020-04-02 08:21:12 +00:00
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,
2020-04-03 09:19:18 +00:00
noPrune: true,
2020-04-02 08:21:12 +00:00
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) {
2020-04-17 13:30:27 +00:00
var start = 0;
2020-04-02 08:21:12 +00:00
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}),
2020-04-02 08:21:12 +00:00
right = h('span.fa.fa-chevron-right'),
]),
h('br'),
replay = h('pre.cp-debug-replay'),
]);
var $start = $(start);
2020-04-02 08:21:12 +00:00
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; }
2020-04-02 08:21:12 +00:00
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);
2020-04-02 08:21:12 +00:00
$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;
}
2020-04-02 08:21:12 +00:00
$(replay).text(JSON.stringify(JSON.parse(chainpad.getUserDoc()), 0, 2));
};
play(1);
$left.click(function () {
play(i-1);
});
$right.click(function () {
play(i+1);
});
2020-04-02 08:21:12 +00:00
$input.keydown(function (e) {
if ([37, 38, 39, 40].indexOf(e.which) !== -1) {
2020-04-02 08:21:12 +00:00
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);
});
2020-04-02 08:21:12 +00:00
}, {timeout: 2147483647}); // Max 32-bit integer
};
2019-02-25 17:43:32 +00:00
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');
2020-04-02 08:21:12 +00:00
var buttonReplay = h('button.btn.btn-success', 'Replay');
2019-02-25 17:43:32 +00:00
$(button).click(getFullHistory);
2020-04-02 08:21:12 +00:00
$(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
2019-02-25 17:43:32 +00:00
h('br'),
2020-04-02 08:21:12 +00:00
button,
buttonReplay
2019-02-25 17:43:32 +00:00
]);
$('#cp-app-debug-content').html('').append(content);
};
setInitContent();
2017-11-21 16:37:38 +00:00
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) {
2019-02-25 17:43:32 +00:00
$('#cp-app-debug-history').text(JSON.stringify(doc, 0, 2));
2017-11-21 16:37:38 +00:00
console.log(doc);
};
var toRestore;
2019-11-22 17:02:33 +00:00
config.onLocal = function (a, restore) {
if (!toRestore || !restore) { return; }
cpNfInner.chainpad.contentUpdate(toRestore);
};
2017-11-21 16:37:38 +00:00
config.onInit = function (info) {
Title = common.createTitle({});
var configTb = {
2020-06-26 13:58:03 +00:00
displayed: ['pad'],
2017-11-21 16:37:38 +00:00
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 = {
2019-11-22 17:02:33 +00:00
onLocal: function () {
config.onLocal(null, true);
},
2017-11-21 16:37:38 +00:00
onRemote: config.onRemote,
setHistory: setHistory,
applyVal: function (val) {
toRestore = val;
2017-11-21 16:37:38 +00:00
displayDoc(JSON.parse(val) || {});
},
$toolbar: $bar,
debug: true
};
var $hist = common.createButton('history', true, {histConfig: histConfig});
$hist.addClass('cp-hidden-if-readonly');
2020-06-26 13:58:03 +00:00
toolbar.$drawer.append($hist);
2019-02-19 13:31:05 +00:00
2019-02-25 17:43:32 +00:00
var $content = common.createButton(null, true, {
icon: 'fa-question',
title: 'Get debugging graph', // TODO
2019-02-19 13:31:05 +00:00
name: 'graph',
2019-02-25 17:43:32 +00:00
id: 'cp-app-debug-get-content'
2019-02-19 13:31:05 +00:00
});
2019-02-25 17:43:32 +00:00
$content.click(getContent);
2020-06-26 13:58:03 +00:00
toolbar.$drawer.append($content);
2017-11-21 16:37:38 +00:00
};
config.onReady = function (info) {
if (APP.realtime !== info.realtime) {
2017-11-30 17:22:26 +00:00
APP.realtime = info.realtime;
2017-11-21 16:37:38 +00:00
}
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);
}
2019-02-25 17:43:32 +00:00
metadataMgr.updateTitle('');
2017-11-21 16:37:38 +00:00
initializing = false;
2019-02-25 17:43:32 +00:00
$('#cp-app-debug-history').show();
2017-11-21 16:37:38 +00:00
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);
};
2017-11-21 16:42:50 +00:00
cpNfInner = APP.cpNfInner = common.startRealtime(config);
metadataMgr = APP.metadataMgr = cpNfInner.metadataMgr;
2017-11-21 16:37:38 +00:00
cpNfInner.onInfiniteSpinner(function () {
console.error('infinite spinner');
});
});
});