You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cryptpad/www/debug/inner.js

717 lines
27 KiB
JavaScript

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

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);
};
var extractMetadata = function (content) {
if (Array.isArray(content)) {
var m = content[content.length - 1];
if (typeof(m.metadata) === 'object') {
// pad
return m.metadata;
}
} else if (typeof(content.metadata) === 'object') {
return content.metadata;
}
return;
};
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,
extractMetadata: extractMetadata,
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');
});
});
});