Added cryptsheet to cryptpad suite!
parent
4a63ba7df3
commit
50c10f818e
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"directory" : "www/bower"
|
"directory" : "www/bower_components"
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
www/bower/*
|
www/bower_components/*
|
||||||
node_modules
|
node_modules
|
||||||
/config.js
|
/config.js
|
||||||
|
@ -0,0 +1,172 @@
|
|||||||
|
var Toolbar = function ($, container, Messages, myUserName, realtime) {
|
||||||
|
|
||||||
|
/** Id of the element for getting debug info. */
|
||||||
|
var DEBUG_LINK_CLS = 'rtwysiwyg-debug-link';
|
||||||
|
|
||||||
|
/** Id of the div containing the user list. */
|
||||||
|
var USER_LIST_CLS = 'rtwysiwyg-user-list';
|
||||||
|
|
||||||
|
/** Id of the div containing the lag info. */
|
||||||
|
var LAG_ELEM_CLS = 'rtwysiwyg-lag';
|
||||||
|
|
||||||
|
/** The toolbar class which contains the user list, debug link and lag. */
|
||||||
|
var TOOLBAR_CLS = 'rtwysiwyg-toolbar';
|
||||||
|
|
||||||
|
/** Key in the localStore which indicates realtime activity should be disallowed. */
|
||||||
|
var LOCALSTORAGE_DISALLOW = 'rtwysiwyg-disallow';
|
||||||
|
|
||||||
|
var SPINNER_DISAPPEAR_TIME = 3000;
|
||||||
|
var SPINNER = [ '-', '\\', '|', '/' ];
|
||||||
|
|
||||||
|
var uid = function () {
|
||||||
|
return 'rtwysiwyg-uid-' + String(Math.random()).substring(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
var createRealtimeToolbar = function (container) {
|
||||||
|
var id = uid();
|
||||||
|
$(container).prepend(
|
||||||
|
'<div class="' + TOOLBAR_CLS + '" id="' + id + '">' +
|
||||||
|
'<div class="rtwysiwyg-toolbar-leftside"></div>' +
|
||||||
|
'<div class="rtwysiwyg-toolbar-rightside"></div>' +
|
||||||
|
'</div>'
|
||||||
|
);
|
||||||
|
var toolbar = $('#'+id);
|
||||||
|
toolbar.append([
|
||||||
|
'<style>',
|
||||||
|
'.' + TOOLBAR_CLS + ' {',
|
||||||
|
' color: #666;',
|
||||||
|
' font-weight: bold;',
|
||||||
|
// ' background-color: #f0f0ee;',
|
||||||
|
// ' border-bottom: 1px solid #DDD;',
|
||||||
|
// ' border-top: 3px solid #CCC;',
|
||||||
|
// ' border-right: 2px solid #CCC;',
|
||||||
|
// ' border-left: 2px solid #CCC;',
|
||||||
|
' height: 26px;',
|
||||||
|
' margin-bottom: -3px;',
|
||||||
|
' display: inline-block;',
|
||||||
|
' width: 100%;',
|
||||||
|
'}',
|
||||||
|
'.' + TOOLBAR_CLS + ' div {',
|
||||||
|
' padding: 0 10px;',
|
||||||
|
' height: 1.5em;',
|
||||||
|
// ' background: #f0f0ee;',
|
||||||
|
' line-height: 25px;',
|
||||||
|
' height: 22px;',
|
||||||
|
'}',
|
||||||
|
'.rtwysiwyg-toolbar-leftside {',
|
||||||
|
' float: left;',
|
||||||
|
'}',
|
||||||
|
'.rtwysiwyg-toolbar-rightside {',
|
||||||
|
' float: right;',
|
||||||
|
'}',
|
||||||
|
'.rtwysiwyg-lag {',
|
||||||
|
' float: right;',
|
||||||
|
'}',
|
||||||
|
'.rtwysiwyg-spinner {',
|
||||||
|
' float: left;',
|
||||||
|
'}',
|
||||||
|
'.gwt-TabBar {',
|
||||||
|
' display:none;',
|
||||||
|
'}',
|
||||||
|
'.' + DEBUG_LINK_CLS + ':link { color:transparent; }',
|
||||||
|
'.' + DEBUG_LINK_CLS + ':link:hover { color:blue; }',
|
||||||
|
'.gwt-TabPanelBottom { border-top: 0 none; }',
|
||||||
|
|
||||||
|
'</style>'
|
||||||
|
].join('\n'));
|
||||||
|
return toolbar;
|
||||||
|
};
|
||||||
|
|
||||||
|
var createSpinner = function (container) {
|
||||||
|
var id = uid();
|
||||||
|
$(container).append('<div class="rtwysiwyg-spinner" id="'+id+'"></div>');
|
||||||
|
return $('#'+id)[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
var kickSpinner = function (spinnerElement, reversed) {
|
||||||
|
var txt = spinnerElement.textContent || '-';
|
||||||
|
var inc = (reversed) ? -1 : 1;
|
||||||
|
spinnerElement.textContent = SPINNER[(SPINNER.indexOf(txt) + inc) % SPINNER.length];
|
||||||
|
spinnerElement.timeout && clearTimeout(spinnerElement.timeout);
|
||||||
|
spinnerElement.timeout = setTimeout(function () {
|
||||||
|
spinnerElement.textContent = '';
|
||||||
|
}, SPINNER_DISAPPEAR_TIME);
|
||||||
|
};
|
||||||
|
|
||||||
|
var createUserList = function (container) {
|
||||||
|
var id = uid();
|
||||||
|
$(container).prepend('<div class="' + USER_LIST_CLS + '" id="'+id+'"></div>');
|
||||||
|
return $('#'+id)[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateUserList = function (myUserName, listElement, userList) {
|
||||||
|
var meIdx = userList.indexOf(myUserName);
|
||||||
|
if (meIdx === -1) {
|
||||||
|
listElement.textContent = Messages.synchronizing;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (userList.length === 1) {
|
||||||
|
listElement.textContent = Messages.editingAlone;
|
||||||
|
} else if (userList.length === 2) {
|
||||||
|
listElement.textContent = Messages.editingWithOneOtherPerson;
|
||||||
|
} else {
|
||||||
|
listElement.textContent = Messages.editingWith + ' ' + (userList.length - 1) + ' '
|
||||||
|
Messages.otherPeople;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var createLagElement = function (container) {
|
||||||
|
var id = uid();
|
||||||
|
$(container).append('<div class="' + LAG_ELEM_CLS + '" id="'+id+'"></div>');
|
||||||
|
return $('#'+id)[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
var checkLag = function (realtime, lagElement) {
|
||||||
|
var lag = realtime.getLag();
|
||||||
|
var lagSec = lag.lag/1000;
|
||||||
|
var lagMsg = Messages.lag + ' ';
|
||||||
|
if (lag.waiting && lagSec > 1) {
|
||||||
|
lagMsg += "?? " + Math.floor(lagSec);
|
||||||
|
} else {
|
||||||
|
lagMsg += lagSec;
|
||||||
|
}
|
||||||
|
lagElement.textContent = lagMsg;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var toolbar = createRealtimeToolbar(container);
|
||||||
|
var userListElement = createUserList(toolbar.find('.rtwysiwyg-toolbar-leftside'));
|
||||||
|
var spinner = createSpinner(toolbar.find('.rtwysiwyg-toolbar-rightside'));
|
||||||
|
var lagElement = createLagElement(toolbar.find('.rtwysiwyg-toolbar-rightside'));
|
||||||
|
|
||||||
|
var connected = false;
|
||||||
|
|
||||||
|
realtime.onUserListChange(function (userList) {
|
||||||
|
if (userList.indexOf(myUserName) !== -1) { connected = true; }
|
||||||
|
if (!connected) { return; }
|
||||||
|
updateUserList(myUserName, userListElement, userList);
|
||||||
|
});
|
||||||
|
|
||||||
|
var ks = function () {
|
||||||
|
if (connected) { kickSpinner(spinner, false); }
|
||||||
|
};
|
||||||
|
|
||||||
|
realtime.onPatch(ks);
|
||||||
|
realtime.onMessage(ks);
|
||||||
|
|
||||||
|
setInterval(function () {
|
||||||
|
if (!connected) { return; }
|
||||||
|
checkLag(realtime, lagElement);
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
return {
|
||||||
|
reconnecting: function () {
|
||||||
|
connected = false;
|
||||||
|
userListElement.textContent = Messages.reconnecting;
|
||||||
|
lagElement.textContent = '';
|
||||||
|
},
|
||||||
|
connected: function () {
|
||||||
|
connected = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
|
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<textarea style="display:none" cols="80" id="editor1" name="editor1" rows="10"></textarea>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,29 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||||
|
<script data-main="main" src="/bower_components/requirejs/require.js"></script>
|
||||||
|
<style>
|
||||||
|
iframe {
|
||||||
|
position:fixed;
|
||||||
|
top:0px;
|
||||||
|
left:0px;
|
||||||
|
bottom:0px;
|
||||||
|
right:0px;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
border:none;
|
||||||
|
margin:0;
|
||||||
|
padding:0;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
display:none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<iframe src="inner.html"></iframe>
|
||||||
|
<textarea id="sheet-json"></textarea>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=Edge"/>
|
||||||
|
<meta http-equiv="Pragma" content="no-cache">
|
||||||
|
<meta http-equiv="Expires" content="-1">
|
||||||
|
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
|
||||||
|
<meta content="utf-8" http-equiv="encoding">
|
||||||
|
<title>jQuery.sheet - The Ajax Spreadsheet DTS Lazy Loading</title>
|
||||||
|
<script src="/bower_components/jquery-legacy/dist/jquery.js"></script>
|
||||||
|
<script src="/bower_components/jquery.sheet/parser/formula/formula.js"></script>
|
||||||
|
<script src="/bower_components/jquery.sheet/jquery.sheet.js"></script>
|
||||||
|
<script>
|
||||||
|
var numColumns = 10;
|
||||||
|
var numRows = 20;
|
||||||
|
var spreadsheetJson = [ { "title": "", "rows": [ ] } ];
|
||||||
|
for (var i = 0; i < numRows; i++) {
|
||||||
|
var columns = [];
|
||||||
|
spreadsheetJson[0].rows.push({ columns: columns });
|
||||||
|
for (var j = 0; j < numColumns; j++) { columns[j] = { "value": "" }; }
|
||||||
|
}
|
||||||
|
|
||||||
|
$.sheet.preLoad('/bower_components/jquery.sheet/');
|
||||||
|
$(function () {
|
||||||
|
window.sheetLdr = new Sheet.JSONLoader( spreadsheetJson );
|
||||||
|
window.sh = $('#sheet-json').height($(window).height()).width($(window).width()).sheet({
|
||||||
|
loader: window.sheetLdr,
|
||||||
|
minSize: { rows:numRows, cols:numColumns }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="sheet-json" title="CryptSheet"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,201 @@
|
|||||||
|
define([
|
||||||
|
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||||
|
'/common/messages.js',
|
||||||
|
'/common/toolbar.js',
|
||||||
|
'/common/chainpad.js',
|
||||||
|
'/bower_components/jquery/dist/jquery.min.js',
|
||||||
|
'/bower_components/tweetnacl/nacl-fast.min.js',
|
||||||
|
'/common/otaml.js'
|
||||||
|
], function (Config, Messages) {
|
||||||
|
var Nacl = window.nacl;
|
||||||
|
var $ = jQuery;
|
||||||
|
var ChainPad = window.ChainPad;
|
||||||
|
var Otaml = window.Otaml;
|
||||||
|
var Toolbar = window.Toolbar;
|
||||||
|
|
||||||
|
var module = { exports: {} };
|
||||||
|
|
||||||
|
var parseKey = function (str) {
|
||||||
|
var array = Nacl.util.decodeBase64(str);
|
||||||
|
var hash = Nacl.hash(array);
|
||||||
|
return { lookupKey: hash.subarray(32), cryptKey: hash.subarray(0,32) };
|
||||||
|
};
|
||||||
|
|
||||||
|
var genKey = function () {
|
||||||
|
return Nacl.util.encodeBase64(Nacl.randomBytes(18));
|
||||||
|
};
|
||||||
|
|
||||||
|
var userName = function () {
|
||||||
|
return 'Other-' + Nacl.util.encodeBase64(Nacl.randomBytes(8));
|
||||||
|
};
|
||||||
|
|
||||||
|
var sheetToJson = function (ifrWindow) {
|
||||||
|
var xx = ifrWindow.sh[0].jS
|
||||||
|
var m = [];
|
||||||
|
for (var i = 0; i < xx.spreadsheets.length; i++) {
|
||||||
|
m[i]=[];
|
||||||
|
var sheet = xx.spreadsheets[i];
|
||||||
|
for (var j = 1; j < sheet.length; j++) {
|
||||||
|
m[i][j]=[];
|
||||||
|
var row = sheet[j];
|
||||||
|
for (var k = 1; k < row.length; k++) {
|
||||||
|
var col = row[k];
|
||||||
|
m[i][j][k] = { value: col.value, formula: col.formula };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
};
|
||||||
|
|
||||||
|
var jsonToSheet = function (ifrWindow, json) {
|
||||||
|
var xx = ifrWindow.sh[0].jS;
|
||||||
|
for (var i = 0; i < xx.spreadsheets.length; i++) {
|
||||||
|
var sheet = xx.spreadsheets[i];
|
||||||
|
for (var j = 1; j < sheet.length; j++) {
|
||||||
|
var row = sheet[j];
|
||||||
|
for (var k = 1; k < row.length; k++) {
|
||||||
|
var col = row[k];
|
||||||
|
var jcol = json[i][j][k];
|
||||||
|
if (jcol.value === col.value && jcol.formula === col.formula) { continue; }
|
||||||
|
col.value = jcol.value;
|
||||||
|
col.formula = jcol.formula;
|
||||||
|
col.displayValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var encryptStr = function (str, key) {
|
||||||
|
var array = Nacl.util.decodeUTF8(str);
|
||||||
|
var nonce = Nacl.randomBytes(24);
|
||||||
|
var packed = Nacl.secretbox(array, nonce, key);
|
||||||
|
if (!packed) { throw new Error(); }
|
||||||
|
return Nacl.util.encodeBase64(nonce) + "|" + Nacl.util.encodeBase64(packed);
|
||||||
|
};
|
||||||
|
var decryptStr = function (str, key) {
|
||||||
|
var arr = str.split('|');
|
||||||
|
if (arr.length !== 2) { throw new Error(); }
|
||||||
|
var nonce = Nacl.util.decodeBase64(arr[0]);
|
||||||
|
var packed = Nacl.util.decodeBase64(arr[1]);
|
||||||
|
var unpacked = Nacl.secretbox.open(packed, nonce, key);
|
||||||
|
if (!unpacked) { throw new Error(); }
|
||||||
|
return Nacl.util.encodeUTF8(unpacked);
|
||||||
|
};
|
||||||
|
|
||||||
|
// this is crap because of bencoding messages... it should go away....
|
||||||
|
var splitMessage = function (msg, sending) {
|
||||||
|
var idx = 0;
|
||||||
|
var nl;
|
||||||
|
for (var i = ((sending) ? 0 : 1); i < 3; i++) {
|
||||||
|
nl = msg.indexOf(':',idx);
|
||||||
|
idx = nl + Number(msg.substring(idx,nl)) + 1;
|
||||||
|
}
|
||||||
|
return [ msg.substring(0,idx), msg.substring(msg.indexOf(':',idx) + 1) ];
|
||||||
|
};
|
||||||
|
|
||||||
|
var encrypt = function (msg, key) {
|
||||||
|
var spl = splitMessage(msg, true);
|
||||||
|
var json = JSON.parse(spl[1]);
|
||||||
|
// non-patches are not encrypted.
|
||||||
|
if (json[0] !== 2) { return msg; }
|
||||||
|
json[1] = encryptStr(JSON.stringify(json[1]), key);
|
||||||
|
var res = JSON.stringify(json);
|
||||||
|
return spl[0] + res.length + ':' + res;
|
||||||
|
};
|
||||||
|
|
||||||
|
var decrypt = function (msg, key) {
|
||||||
|
var spl = splitMessage(msg, false);
|
||||||
|
var json = JSON.parse(spl[1]);
|
||||||
|
// non-patches are not encrypted.
|
||||||
|
if (json[0] !== 2) { return msg; }
|
||||||
|
if (typeof(json[1]) !== 'string') { throw new Error(); }
|
||||||
|
json[1] = JSON.parse(decryptStr(json[1], key));
|
||||||
|
var res = JSON.stringify(json);
|
||||||
|
return spl[0] + res.length + ':' + res;
|
||||||
|
};
|
||||||
|
|
||||||
|
var applyChange = function(ctx, oldval, newval) {
|
||||||
|
if (oldval === newval) return;
|
||||||
|
|
||||||
|
var commonStart = 0;
|
||||||
|
while (oldval.charAt(commonStart) === newval.charAt(commonStart)) {
|
||||||
|
commonStart++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var commonEnd = 0;
|
||||||
|
while (oldval.charAt(oldval.length - 1 - commonEnd) === newval.charAt(newval.length - 1 - commonEnd) &&
|
||||||
|
commonEnd + commonStart < oldval.length && commonEnd + commonStart < newval.length) {
|
||||||
|
commonEnd++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldval.length !== commonStart + commonEnd) {
|
||||||
|
ctx.remove(commonStart, oldval.length - commonStart - commonEnd);
|
||||||
|
}
|
||||||
|
if (newval.length !== commonStart + commonEnd) {
|
||||||
|
ctx.insert(commonStart, newval.slice(commonStart, newval.length - commonEnd));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
|
||||||
|
if (window.location.href.indexOf('#') === -1) {
|
||||||
|
window.location.href = window.location.href + '#' + genKey();
|
||||||
|
}
|
||||||
|
$(window).on('hashchange', function() {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
var $sheetJson = $('#sheet-json');
|
||||||
|
var ifrw = $('iframe')[0].contentWindow;
|
||||||
|
var sheetEvent = function (realtime) {
|
||||||
|
var sheetJson = JSON.stringify(sheetToJson(ifrw));
|
||||||
|
applyChange(realtime, realtime.getUserDoc(), sheetJson);
|
||||||
|
$sheetJson.text(sheetJson);
|
||||||
|
};
|
||||||
|
|
||||||
|
var eventPending = false;
|
||||||
|
var realtimeEvent = function (realtime) {
|
||||||
|
if (eventPending) { return; }
|
||||||
|
eventPending = true;
|
||||||
|
setTimeout(function () {
|
||||||
|
eventPending = false;
|
||||||
|
try{
|
||||||
|
var data = window.data = realtime.getUserDoc();
|
||||||
|
$sheetJson.text(data);
|
||||||
|
var json = JSON.parse(data);
|
||||||
|
jsonToSheet(ifrw, json);
|
||||||
|
}catch(e) { console.log(e.stack); }
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
var key = parseKey(window.location.hash.substring(1));
|
||||||
|
var channel = Nacl.util.encodeBase64(key.lookupKey).substring(0,10);
|
||||||
|
var myUserName = userName();
|
||||||
|
|
||||||
|
var socket = new WebSocket(Config.websocketURL);
|
||||||
|
socket.onopen = function () {
|
||||||
|
var realtime = ChainPad.create(
|
||||||
|
myUserName, 'x', channel, '', { transformFunction: Otaml.transform });
|
||||||
|
socket.onmessage = function (evt) {
|
||||||
|
var message = decrypt(evt.data, key.cryptKey);
|
||||||
|
realtime.message(message);
|
||||||
|
};
|
||||||
|
realtime.onMessage(function (message) {
|
||||||
|
message = encrypt(message, key.cryptKey);
|
||||||
|
try {
|
||||||
|
socket.send(message);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e.stack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ifrw.sh.on('sheetCellEdited', function () { sheetEvent(realtime); });
|
||||||
|
sheetEvent(realtime);
|
||||||
|
realtime.onPatch(function () { realtimeEvent(realtime); });
|
||||||
|
|
||||||
|
ifrw.$('.jSTitle').html('');
|
||||||
|
Toolbar(ifrw.$, ifrw.$('.jSTitle'), Messages, myUserName, realtime);
|
||||||
|
|
||||||
|
realtime.start();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue