mirror of https://github.com/xwiki-labs/cryptpad
776 lines
30 KiB
JavaScript
776 lines
30 KiB
JavaScript
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||
//
|
||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||
|
||
define([
|
||
'jquery',
|
||
'/components/chainpad-crypto/crypto.js',
|
||
'/common/toolbar.js',
|
||
'json.sortify',
|
||
'/common/common-util.js',
|
||
'/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',
|
||
'/common/common-ui-elements.js',
|
||
'/debug/chainpad.dist.js',
|
||
|
||
'css!/components/bootstrap/dist/css/bootstrap.min.css',
|
||
'css!/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,
|
||
UIElements,
|
||
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(); }
|
||
else {
|
||
setTimeout(cpNfInner.metadataMgr.refresh);
|
||
}
|
||
return true;
|
||
};
|
||
|
||
var displayDoc = function (doc) {
|
||
$('#cp-app-debug-history').text(JSON.stringify(doc, 0, 2));
|
||
console.log(doc);
|
||
};
|
||
|
||
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;
|
||
};
|
||
|
||
// Get the realtime metadata when in history mode
|
||
var getLastMetadata = function () {
|
||
var newContentStr = cpNfInner.chainpad.getUserDoc();
|
||
var newContent = JSON.parse(newContentStr);
|
||
var meta = extractMetadata(newContent);
|
||
return meta;
|
||
};
|
||
var setLastMetadata = function (md) {
|
||
var newContentStr = cpNfInner.chainpad.getAuthDoc();
|
||
var newContent = JSON.parse(newContentStr);
|
||
if (Array.isArray(newContent)) {
|
||
newContent[3] = {
|
||
metadata: md
|
||
};
|
||
} else {
|
||
newContent.metadata = md;
|
||
}
|
||
try {
|
||
cpNfInner.chainpad.contentUpdate(JSONSortify(newContent));
|
||
return true;
|
||
} catch (e) {
|
||
console.error(e);
|
||
return false;
|
||
}
|
||
};
|
||
|
||
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 () {
|
||
// The following lines allow us to restore an old version from the debug app
|
||
// without affecting the snapshots.
|
||
// It's parsing, updating and stringifying text data which is not a clean way
|
||
// to change metadata, so we're disabling it by default.
|
||
if (window.cp_snapshots) {
|
||
var md = Util.clone(cpNfInner.metadataMgr.getMetadata());
|
||
var _snapshots = md.snapshots;
|
||
var newContent = JSON.parse(toRestore);
|
||
try {
|
||
if (Array.isArray(newContent)) {
|
||
newContent[3].metadata.snapshots = _snapshots;
|
||
} else {
|
||
newContent.metadata.snapshots = _snapshots;
|
||
}
|
||
} catch (e) { console.error(e); }
|
||
toRestore = JSONSortify(newContent);
|
||
}
|
||
config.onLocal(null, true);
|
||
},
|
||
onRemote: config.onRemote,
|
||
setHistory: setHistory,
|
||
extractMetadata: extractMetadata,
|
||
getLastMetadata: getLastMetadata, // get from authdoc
|
||
setLastMetadata: setLastMetadata, // set to userdoc/authdoc
|
||
applyVal: function (val) {
|
||
toRestore = val;
|
||
var newContent = JSON.parse(val);
|
||
var meta = extractMetadata(newContent);
|
||
cpNfInner.metadataMgr.updateMetadata(meta);
|
||
displayDoc(JSON.parse(val) || {});
|
||
},
|
||
$toolbar: $bar,
|
||
debug: true
|
||
};
|
||
var $hist = common.createButton('history', true, {histConfig: histConfig});
|
||
$hist.addClass('cp-hidden-if-readonly');
|
||
var $histEntry = UIElements.getEntryFromButton($hist);
|
||
toolbar.$drawer.append($histEntry);
|
||
|
||
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);
|
||
var $contentEntry = UIElements.getEntryFromButton($content);
|
||
console.error($contentEntry);
|
||
toolbar.$drawer.append($contentEntry);
|
||
};
|
||
|
||
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');
|
||
});
|
||
});
|
||
|
||
});
|