Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging

This commit is contained in:
Caleb James DeLisle 2017-12-05 18:32:29 +01:00
commit ba74f9d37a
54 changed files with 2418 additions and 1586 deletions

View file

@ -7,7 +7,6 @@
"iterator": true, "iterator": true,
"latedef": true, "latedef": true,
"nocomma": true, "nocomma": true,
"notypeof": true,
"shadow": false, "shadow": false,
"undef": true, "undef": true,
"unused": true, "unused": true,

View file

@ -44,7 +44,8 @@
"open-sans-fontface": "^1.4.2", "open-sans-fontface": "^1.4.2",
"bootstrap-tokenfield": "^0.12.1", "bootstrap-tokenfield": "^0.12.1",
"localforage": "^1.5.2", "localforage": "^1.5.2",
"html2canvas": "^0.4.1" "html2canvas": "^0.4.1",
"croppie": "^2.5.0"
}, },
"resolutions": { "resolutions": {
"bootstrap": "v4.0.0-alpha.6" "bootstrap": "v4.0.0-alpha.6"

View file

@ -159,6 +159,24 @@ module.exports = {
*/ */
defaultStorageLimit: 50 * 1024 * 1024, defaultStorageLimit: 50 * 1024 * 1024,
/*
* CryptPad allows administrators to give custom limits to their friends.
* add an entry for each friend, identified by their user id,
* which can be found on the settings page. Include a 'limit' (number of bytes),
* a 'plan' (string), and a 'note' (string).
*
* hint: 1GB is 1024 * 1024 * 1024 bytes
*/
customLimits: {
/*
"https://my.awesome.website/user/#/1/cryptpad-user/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=": {
limit: 20 * 1024 * 1024 * 1024,
plan: 'insider',
note: 'storage space donated by my.awesome.website'
}
*/
},
/* /*
* By default, CryptPad also contacts our accounts server once a day to check for changes in * By default, CryptPad also contacts our accounts server once a day to check for changes in
* the people who have accounts. This check-in will also send the version of your CryptPad * the people who have accounts. This check-in will also send the version of your CryptPad

16
customize.dist/404.html Normal file
View file

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html class="cp" id="four-oh-four">
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
<head>
<title data-localization="main_title">CryptPad: Zero Knowledge, Collaborative Real Time Editing</title>
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" type="image/png" href="/customize/main-favicon.png" id="favicon"/>
<script async data-bootload="/customize/four-oh-four.js" data-main="/common/boot.js?ver=1.0" src="/bower_components/requirejs/require.js?ver=2.3.5"></script>
</head>
<body class="html">
<noscript>
<h1>404</h1>
<h3>We couldn't find the page you were looking for</h3>
</noscript>

View file

@ -0,0 +1,78 @@
define([
'/api/config',
'/common/hyperscript.js',
'/common/outer/local-store.js',
'/customize/messages.js',
'less!/customize/src/less2/pages/page-404.less',
], function (Config, h, LocalStore, Messages) {
var urlArgs = Config.requireConf.urlArgs;
var img = h('img#cp-logo', {
src: '/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs
});
var brand = h('h1#cp-brand', 'CryptPad');
var message = h('h2#cp-scramble', Messages.four04_pageNotFound);
var title = h('h2#cp-title', "404");
var loggedIn = LocalStore.isLoggedIn();
var link = h('a#cp-link', {
href: loggedIn? '/drive/': '/',
}, loggedIn? Messages.header_logoTitle: Messages.header_homeTitle);
var content = h('div#cp-main', [
img,
brand,
title,
message,
link,
]);
document.body.appendChild(content);
var die = function (n) { return Math.floor(Math.random() * n); };
var randomChar = function () {
return String.fromCharCode(die(94) + 34);
};
var mutate = function (S, i, c) {
var A = S.split("");
A[i] = c;
return A.join("");
};
var take = function (A) {
var n = die(A.length);
var choice = A[n];
A.splice(n, 1);
return choice;
};
var makeDecryptor = function (el, t, difficulty, cb) {
var Orig = el.innerText;
var options = [];
el.innerText = el.innerText.split("").map(function (c, i) {
Orig[i] = c;
options.push(i);
return randomChar();
}).join("");
return function f () {
if (die(difficulty) === 0) {
var choice = take(options);
el.innerText = mutate(el.innerText, choice, Orig.charAt(choice));
} else { // make a superficial change
el.innerText = mutate(el.innerText,
options[die(options.length)],
randomChar());
}
setTimeout(options.length > 0? f: cb, t);
};
};
makeDecryptor(brand, 70, 2, function () { })();
makeDecryptor(title, 50, 14, function () { })();
makeDecryptor(link, 20, 4, function () {})();
makeDecryptor(message, 12, 3, function () {
console.log('done');
})();
});

View file

@ -12,7 +12,7 @@ var map = {
var messages = {}; var messages = {};
var LS_LANG = "CRYPTPAD_LANG"; var LS_LANG = "CRYPTPAD_LANG";
var getStoredLanguage = function () { return localStorage.getItem(LS_LANG); }; var getStoredLanguage = function () { return localStorage && localStorage.getItem(LS_LANG); };
var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage || ''; }; var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage || ''; };
var getLanguage = messages._getLanguage = function () { var getLanguage = messages._getLanguage = function () {
if (window.cryptpadLanguage) { return window.cryptpadLanguage; } if (window.cryptpadLanguage) { return window.cryptpadLanguage; }
@ -24,19 +24,17 @@ var getLanguage = messages._getLanguage = function () {
}; };
var language = getLanguage(); var language = getLanguage();
var req = ['jquery', '/customize/translations/messages.js']; var req = ['/common/common-util.js', '/customize/translations/messages.js'];
if (language && map[language]) { req.push('/customize/translations/messages.' + language + '.js'); } if (language && map[language]) { req.push('/customize/translations/messages.' + language + '.js'); }
define(req, function($, Default, Language) { define(req, function(Util, Default, Language) {
map.en = 'English'; map.en = 'English';
var defaultLanguage = 'en'; var defaultLanguage = 'en';
if (!Language || language === defaultLanguage || !map[language]) { Util.extend(messages, Default);
messages = $.extend(true, messages, Default); if (Language && language !== defaultLanguage) {
}
else {
// Add the translated keys to the returned object // Add the translated keys to the returned object
messages = $.extend(true, messages, Default, Language); Util.extend(messages, Language);
} }
messages._languages = map; messages._languages = map;

View file

@ -0,0 +1,40 @@
@import (once) './include/font.less';
.font_neuropolitical();
.font_open-sans();
body.cp-page-index { @import "./pages/page-index.less"; }
body.cp-page-contact { @import "./pages/page-contact.less"; }
body.cp-page-login { @import "./pages/page-login.less"; }
body.cp-page-register { @import "./pages/page-register.less"; }
body.cp-page-what-is-cryptpad { @import "./pages/page-what-is-cryptpad.less"; }
body.cp-page-about { @import "./pages/page-about.less"; }
body.cp-page-privacy { @import "./pages/page-privacy.less"; }
body.cp-page-terms { @import "./pages/page-terms.less"; }
// Set the HTML style for the apps which shouldn't have a body scrollbar
html.cp-app-noscroll {
@import "./include/app-noscroll.less";
.app-noscroll_main();
}
// Set the HTML style for printing slides
html.cp-app-print {
@import "./include/app-print.less";
.app-print_main();
}
body.cp-readonly .cp-hidden-if-readonly { display:none !important; }
body.cp-app-drive { @import "../../../drive/app-drive.less"; }
body.cp-app-pad { @import "../../../pad/app-pad.less"; }
body.cp-app-code { @import "../../../code/app-code.less"; }
body.cp-app-slide { @import "../../../slide/app-slide.less"; }
body.cp-app-file { @import "../../../file/app-file.less"; }
body.cp-app-filepicker { @import "../../../filepicker/app-filepicker.less"; }
body.cp-app-contacts { @import "../../../contacts/app-contacts.less"; }
body.cp-app-poll { @import "../../../poll/app-poll.less"; }
body.cp-app-whiteboard { @import "../../../whiteboard/app-whiteboard.less"; }
body.cp-app-todo { @import "../../../todo/app-todo.less"; }
body.cp-app-profile { @import "../../../profile/app-profile.less"; }
body.cp-app-settings { @import "../../../settings/app-settings.less"; }
body.cp-app-debug { @import "../../../debug/app-debug.less"; }

View file

@ -0,0 +1,37 @@
@import (once) "../include/colortheme.less";
@import (once) "../include/font.less";
.font_neuropolitical();
.font_open-sans();
html, body {
margin: 0px;
padding: 0px;
#cp-main {
height: 100vh;
margin: 0px;
width: 100%;
padding-top: 5%;
text-align: center;
#cp-logo {
display: block;
max-width: 15%;
margin: auto;
}
#cp-brand {
font-family: neuropolitical;
font-size: 40px;
}
#cp-title {
font-size: 30px;
}
#cp-scramble, #cp-link {
font-size: 20px;
}
#cp-title, #cp-scramble, #cp-link {
//font-family: 'Open Sans';
font-family: monospace;
}
}
}

View file

@ -298,6 +298,8 @@ define(function () {
out.profile_namePlaceholder = 'Nom ou pseudo pour le profil'; out.profile_namePlaceholder = 'Nom ou pseudo pour le profil';
out.profile_avatar = "Avatar"; out.profile_avatar = "Avatar";
out.profile_upload = " Importer un nouvel avatar"; out.profile_upload = " Importer un nouvel avatar";
out.profile_uploadSizeError = "Erreur : votre avatar doit avoir une taille inférieure à {0}";
out.profile_uploadTypeError = "Erreur : le format de votre avatar est invalide. Les formats autorisés sont : {0}";
out.profile_error = "Erreur lors de la création du profil : {0}"; out.profile_error = "Erreur lors de la création du profil : {0}";
out.profile_register = "Vous devez vous inscrire pour pouvoir créer un profil !"; out.profile_register = "Vous devez vous inscrire pour pouvoir créer un profil !";
out.profile_create = "Créer un profil"; out.profile_create = "Créer un profil";
@ -681,10 +683,8 @@ define(function () {
out.tos_logs = "Les meta-données fournies par votre navigateur au serveur peuvent être enregistrées dans le but de maintenir le service."; out.tos_logs = "Les meta-données fournies par votre navigateur au serveur peuvent être enregistrées dans le but de maintenir le service.";
out.tos_3rdparties = "Nous ne fournissons aucune donnée individuelle à des tierces parties à moins d'y être contraints par la loi."; out.tos_3rdparties = "Nous ne fournissons aucune donnée individuelle à des tierces parties à moins d'y être contraints par la loi.";
// BottomBar.html // 404 page
out.four04_pageNotFound = "Nous n'avons pas trouvé la page que vous cherchez.";
out.bottom_france = '<a href="http://www.xwiki.com/fr" target="_blank" rel="noopener noreferrer">Fait avec <img class="bottom-bar-heart" src="/customize/heart.png" alt="amour" /> en <img class="bottom-bar-fr" src="/customize/fr.png" alt="France" /></a>';
out.bottom_support = '<a href="http://labs.xwiki.com/" title="XWiki Labs" target="_blank" rel="noopener noreferrer">Un projet <img src="/customize/logo-xwiki2.png" alt="XWiki SAS" class="bottom-bar-xwiki"/> Labs</a> avec le soutien de <a href="http://ng.open-paas.org/" title="OpenPaaS::ng" target="_blank" rel="noopener noreferrer"> <img src="/customize/openpaasng.png" alt="OpenPaaS-ng" class="bottom-bar-openpaas" /></a>';
// Header.html // Header.html

View file

@ -302,6 +302,8 @@ define(function () {
out.profile_namePlaceholder = 'Name displayed in your profile'; out.profile_namePlaceholder = 'Name displayed in your profile';
out.profile_avatar = "Avatar"; out.profile_avatar = "Avatar";
out.profile_upload = " Upload a new avatar"; out.profile_upload = " Upload a new avatar";
out.profile_uploadSizeError = "Error: your avatar must be smaller than {0}";
out.profile_uploadTypeError = "Error: your avatar type is not allowed. Allowed types are: {0}";
out.profile_error = "Error while creating your profile: {0}"; out.profile_error = "Error while creating your profile: {0}";
out.profile_register = "You have to sign up to create a profile!"; out.profile_register = "You have to sign up to create a profile!";
out.profile_create = "Create a profile"; out.profile_create = "Create a profile";
@ -689,10 +691,13 @@ define(function () {
out.tos_logs = "Metadata provided by your browser to the server may be logged for the purpose of maintaining the service."; out.tos_logs = "Metadata provided by your browser to the server may be logged for the purpose of maintaining the service.";
out.tos_3rdparties = "We do not provide individualized data to third parties unless required to by law."; out.tos_3rdparties = "We do not provide individualized data to third parties unless required to by law.";
// 404 page
out.four04_pageNotFound = "We couldn't find the page you were looking for.";
// BottomBar.html // BottomBar.html
out.bottom_france = '<a href="http://www.xwiki.com/" target="_blank" rel="noopener noreferrer">Made with <img class="bottom-bar-heart" src="/customize/heart.png" alt="love" /> in <img class="bottom-bar-fr" src="/customize/fr.png" alt="France" /></a>'; //out.bottom_france = '<a href="http://www.xwiki.com/" target="_blank" rel="noopener noreferrer">Made with <img class="bottom-bar-heart" src="/customize/heart.png" alt="love" /> in <img class="bottom-bar-fr" src="/customize/fr.png" alt="France" /></a>';
out.bottom_support = '<a href="http://labs.xwiki.com/" title="XWiki Labs" target="_blank" rel="noopener noreferrer">An <img src="/customize/logo-xwiki2.png" alt="XWiki SAS" class="bottom-bar-xwiki"/> Labs Project </a> with the support of <a href="http://ng.open-paas.org/" title="OpenPaaS::ng" target="_blank" rel="noopener noreferrer"> <img src="/customize/openpaasng.png" alt="OpenPaaS-ng" class="bottom-bar-openpaas" /></a>'; //out.bottom_support = '<a href="http://labs.xwiki.com/" title="XWiki Labs" target="_blank" rel="noopener noreferrer">An <img src="/customize/logo-xwiki2.png" alt="XWiki SAS" class="bottom-bar-xwiki"/> Labs Project </a> with the support of <a href="http://ng.open-paas.org/" title="OpenPaaS::ng" target="_blank" rel="noopener noreferrer"> <img src="/customize/openpaasng.png" alt="OpenPaaS-ng" class="bottom-bar-openpaas" /></a>';
// Header.html // Header.html

27
rpc.js
View file

@ -427,6 +427,28 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/)
"Content-Length": Buffer.byteLength(body) "Content-Length": Buffer.byteLength(body)
} }
}; };
// read custom limits from the config
var customLimits = (function (custom) {
var limits = {};
Object.keys(custom).forEach(function (k) {
k.replace(/\/([^\/]+)$/, function (all, safeKey) {
var id = unescapeKeyCharacters(safeKey || '');
limits[id] = custom[k];
return '';
});
});
return limits;
}(config.customLimits || {}));
var isLimit = function (o) {
var valid = o && typeof(o) === 'object' &&
typeof(o.limit) === 'number' &&
typeof(o.plan) === 'string' &&
typeof(o.note) === 'string';
return valid;
};
var req = Https.request(options, function (response) { var req = Https.request(options, function (response) {
if (!('' + response.statusCode).match(/^2\d\d$/)) { if (!('' + response.statusCode).match(/^2\d\d$/)) {
return void cb('SERVER ERROR ' + response.statusCode); return void cb('SERVER ERROR ' + response.statusCode);
@ -441,6 +463,11 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/)
try { try {
var json = JSON.parse(str); var json = JSON.parse(str);
limits = json; limits = json;
Object.keys(customLimits).forEach(function (k) {
if (!isLimit(customLimits[k])) { return; }
limits[k] = customLimits[k];
});
var l; var l;
if (userId) { if (userId) {
var limit = limits[userId]; var limit = limits[userId];

View file

@ -157,6 +157,22 @@ app.get('/api/config', function(req, res){
].join(';\n')); ].join(';\n'));
}); });
var four04_path = Path.resolve(__dirname + '/customize.dist/404.html');
var custom_four04_path = Path.resolve(__dirname + '/customize/404.html');
var send404 = function (res, path) {
if (!path && path !== four04_path) { path = four04_path; }
Fs.exists(path, function (exists) {
if (exists) { return Fs.createReadStream(path).pipe(res); }
send404(res);
});
};
app.use(function (req, res, next) {
res.status(404);
send404(res, custom_four04_path);
});
var httpServer = httpsOpts ? Https.createServer(httpsOpts, app) : Http.createServer(app); var httpServer = httpsOpts ? Https.createServer(httpsOpts, app) : Http.createServer(app);
httpServer.listen(config.httpPort,config.httpAddress,function(){ httpServer.listen(config.httpPort,config.httpAddress,function(){

View file

@ -5,9 +5,11 @@ define([
'/drive/tests.js', '/drive/tests.js',
'/common/test.js', '/common/test.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-util.js',
'/common/common-thumbnail.js', '/common/common-thumbnail.js',
'/common/wire.js',
'/common/flat-dom.js', '/common/flat-dom.js',
], function ($, Hyperjson, Sortify, Drive, Test, Hash, Thumb, Flat) { ], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat) {
window.Hyperjson = Hyperjson; window.Hyperjson = Hyperjson;
window.Sortify = Sortify; window.Sortify = Sortify;
@ -30,7 +32,7 @@ define([
ASSERTS.forEach(function (f, index) { ASSERTS.forEach(function (f, index) {
f(function (err) { f(function (err) {
console.log("test " + index); //console.log("test " + index);
done(err, index); done(err, index);
}, index); }, index);
}); });
@ -235,6 +237,54 @@ define([
return cb(true); return cb(true);
}, "version 2 hash failed to parse correctly"); }, "version 2 hash failed to parse correctly");
assert(function (cb) {
Wire.create({
constructor: function (cb) {
var service = function (type, data, cb) {
switch (type) {
case "HEY_BUDDY":
return cb(void 0, "SALUT!");
default:
cb("ERROR");
}
};
var evt = Util.mkEvent();
var respond = function (e, out) {
evt.fire(e, out);
};
cb(void 0, {
send: function (raw /*, cb */) {
try {
var parsed = JSON.parse(raw);
var txid = parsed.txid;
var message = parsed.message;
setTimeout(function () {
service(message.command, message.content, function (e, result) {
respond(JSON.stringify({
txid: txid,
error: e,
content: result,
}));
});
});
} catch (e) { console.error("PEWPEW"); }
},
receive: function (f) {
evt.reg(f);
},
});
},
}, function (e, rpc) {
if (e) { return cb(false); }
rpc.send('HEY_BUDDY', null, function (e, out) {
if (e) { return void cb(false); }
if (out === 'SALUT!') { cb(true); }
});
});
}, "Test rpc factory");
/* /*
assert(function (cb) { assert(function (cb) {
var getBlob = function (url, cb) { var getBlob = function (url, cb) {

View file

@ -4,8 +4,9 @@ define([
'/common/common-constants.js', '/common/common-constants.js',
'/common/outer/local-store.js', '/common/outer/local-store.js',
'/common/test.js', '/common/test.js',
'/bower_components/nthen/index.js',
'/bower_components/tweetnacl/nacl-fast.min.js' '/bower_components/tweetnacl/nacl-fast.min.js'
], function ($, Cryptpad, Constants, LocalStore, Test) { ], function ($, Cryptpad, Constants, LocalStore, Test, nThen) {
var Nacl = window.nacl; var Nacl = window.nacl;
var signMsg = function (msg, privKey) { var signMsg = function (msg, privKey) {
@ -25,11 +26,18 @@ define([
localStorage[Constants.userHashKey] = localStorage[Constants.userHashKey] || localStorage[Constants.userHashKey] = localStorage[Constants.userHashKey] ||
sessionStorage[Constants.userHashKey]; sessionStorage[Constants.userHashKey];
Cryptpad.ready(function () { var proxy;
nThen(function (waitFor) {
Cryptpad.ready(waitFor());
}).nThen(function (waitFor) {
Cryptpad.getUserObject(waitFor(function (obj) {
proxy = obj;
}));
}).nThen(function () {
console.log('IFRAME READY'); console.log('IFRAME READY');
Test(function () { Test(function () {
// This is only here to maybe trigger an error. // This is only here to maybe trigger an error.
window.drive = Cryptpad.getStore().getProxy().proxy['drive']; window.drive = proxy['drive'];
Test.passed(); Test.passed();
}); });
$(window).on("message", function (jqe) { $(window).on("message", function (jqe) {
@ -46,7 +54,6 @@ define([
} else if (!LocalStore.isLoggedIn()) { } else if (!LocalStore.isLoggedIn()) {
ret.error = "NOT_LOGGED_IN"; ret.error = "NOT_LOGGED_IN";
} else { } else {
var proxy = Cryptpad.getStore().getProxy().proxy;
var sig = signMsg(data.data, proxy.edPrivate); var sig = signMsg(data.data, proxy.edPrivate);
ret.res = { ret.res = {
uname: proxy.login_name, uname: proxy.login_name,

View file

@ -10,5 +10,6 @@ define(function () {
displayNameKey: 'cryptpad.username', displayNameKey: 'cryptpad.username',
oldStorageKey: 'CryptPad_RECENTPADS', oldStorageKey: 'CryptPad_RECENTPADS',
storageKey: 'filesData', storageKey: 'filesData',
tokenKey: 'loginToken',
}; };
}); });

View file

@ -10,7 +10,6 @@ define([
// Add handler to the language selector // Add handler to the language selector
Msg.setLanguage = function (l, sframeChan, cb) { Msg.setLanguage = function (l, sframeChan, cb) {
console.log(sframeChan);
if (sframeChan) { if (sframeChan) {
// We're in the sandbox // We're in the sandbox
sframeChan.query("Q_LANGUAGE_SET", l, cb); sframeChan.query("Q_LANGUAGE_SET", l, cb);

View file

@ -1,15 +1,12 @@
define([ define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/common/curve.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-util.js', '/common/common-util.js',
'/common/common-constants.js', '/common/common-constants.js',
'/customize/messages.js', '/customize/messages.js',
'/bower_components/marked/marked.min.js',
'/common/common-realtime.js', '/common/common-realtime.js',
], function ($, Crypto, Curve, Hash, Util, Constants, Messages, Marked, Realtime) { ], function (Crypto, Hash, Util, Constants, Messages, Realtime) {
var Msg = { var Msg = {
inputs: [], inputs: [],
}; };
@ -51,9 +48,8 @@ define([
}); });
}; };
Msg.getFriendChannelsList = function (common) { Msg.getFriendChannelsList = function (proxy) {
var list = []; var list = [];
var proxy = common.getProxy();
eachFriend(proxy.friends, function (friend) { eachFriend(proxy.friends, function (friend) {
list.push(friend.channel); list.push(friend.channel);
}); });
@ -61,7 +57,7 @@ define([
}; };
// TODO make this internal to the messenger // TODO make this internal to the messenger
var channels = Msg.channels = window.channels = {}; var channels = Msg.channels = {};
Msg.getLatestMessages = function () { Msg.getLatestMessages = function () {
Object.keys(channels).forEach(function (id) { Object.keys(channels).forEach(function (id) {
@ -74,8 +70,8 @@ define([
// Invitation // Invitation
// FIXME there are too many functions with this name // FIXME there are too many functions with this name
var addToFriendList = Msg.addToFriendList = function (common, data, cb) { var addToFriendList = Msg.addToFriendList = function (cfg, data, cb) {
var proxy = common.getProxy(); var proxy = cfg.proxy;
var friends = getFriendList(proxy); var friends = getFriendList(proxy);
var pubKey = data.curvePublic; // todo validata data var pubKey = data.curvePublic; // todo validata data
@ -83,19 +79,19 @@ define([
friends[pubKey] = data; friends[pubKey] = data;
Realtime.whenRealtimeSyncs(common.getRealtime(), function () { Realtime.whenRealtimeSyncs(cfg.realtime, function () {
cb(); cb();
common.pinPads([data.channel], function (e) { cfg.pinPads([data.channel], function (res) {
if (e) { console.error(e); } if (res.error) { console.error(res.error); }
}); });
}); });
common.changeDisplayName(proxy[Constants.displayNameKey]); cfg.updateMetadata();
}; };
/* Used to accept friend requests within apps other than /contacts/ */ /* Used to accept friend requests within apps other than /contacts/ */
Msg.addDirectMessageHandler = function (common) { Msg.addDirectMessageHandler = function (cfg) {
var network = common.getNetwork(); var network = cfg.network;
var proxy = common.getProxy(); var proxy = cfg.proxy;
if (!network) { return void console.error('Network not ready'); } if (!network) { return void console.error('Network not ready'); }
network.on('message', function (message, sender) { network.on('message', function (message, sender) {
var msg; var msg;
@ -138,8 +134,7 @@ define([
var confirmMsg = Messages._getKey('contacts_request', [ var confirmMsg = Messages._getKey('contacts_request', [
Util.fixHTML(msgData.displayName) Util.fixHTML(msgData.displayName)
]); ]);
common.onFriendRequest(confirmMsg, todo); cfg.friendRequest(confirmMsg, todo);
//UI.confirm(confirmMsg, todo, null, true);
return; return;
} }
if (msg[0] === "FRIEND_REQ_OK") { if (msg[0] === "FRIEND_REQ_OK") {
@ -147,14 +142,14 @@ define([
if (idx !== -1) { pendingRequests.splice(idx, 1); } if (idx !== -1) { pendingRequests.splice(idx, 1); }
// FIXME clarify this function's name // FIXME clarify this function's name
addToFriendList(common, msgData, function (err) { addToFriendList(cfg, msgData, function (err) {
if (err) { if (err) {
return void common.onFriendComplete({ return void cfg.friendComplete({
logText: Messages.contacts_addError, logText: Messages.contacts_addError,
netfluxId: sender netfluxId: sender
}); });
} }
common.onFriendComplete({ cfg.friendComplete({
logText: Messages.contacts_added, logText: Messages.contacts_added,
netfluxId: sender netfluxId: sender
}); });
@ -167,24 +162,24 @@ define([
if (msg[0] === "FRIEND_REQ_NOK") { if (msg[0] === "FRIEND_REQ_NOK") {
var i = pendingRequests.indexOf(sender); var i = pendingRequests.indexOf(sender);
if (i !== -1) { pendingRequests.splice(i, 1); } if (i !== -1) { pendingRequests.splice(i, 1); }
common.onFriendComplete({ cfg.friendComplete({
logText: Messages.contacts_rejected, logText: Messages.contacts_rejected,
netfluxId: sender netfluxId: sender
}); });
common.changeDisplayName(proxy[Constants.displayNameKey]); cfg.updateMetadata();
return; return;
} }
if (msg[0] === "FRIEND_REQ_ACK") { if (msg[0] === "FRIEND_REQ_ACK") {
var data = pending[sender]; var data = pending[sender];
if (!data) { return; } if (!data) { return; }
addToFriendList(common, data, function (err) { addToFriendList(cfg, data, function (err) {
if (err) { if (err) {
return void common.onFriendComplete({ return void cfg.friendComplete({
logText: Messages.contacts_addError, logText: Messages.contacts_addError,
netfluxId: sender netfluxId: sender
}); });
} }
common.onFriendComplete({ cfg.friendComplete({
logText: Messages.contacts_added, logText: Messages.contacts_added,
netfluxId: sender netfluxId: sender
}); });
@ -198,17 +193,14 @@ define([
}); });
}; };
Msg.getPending = function () { Msg.inviteFromUserlist = function (cfg, data, cb) {
return pendingRequests; var network = cfg.network;
}; var netfluxId = data.netfluxId;
var parsed = Hash.parsePadUrl(data.href);
Msg.inviteFromUserlist = function (common, netfluxId) {
var network = common.getNetwork();
var parsed = Hash.parsePadUrl(window.location.href);
if (!parsed.hashData) { return; } if (!parsed.hashData) { return; }
// Message // Message
var chan = parsed.hashData.channel; var chan = parsed.hashData.channel;
var myData = createData(common.getProxy()); var myData = createData(cfg.proxy);
var msg = ["FRIEND_REQ", chan, myData]; var msg = ["FRIEND_REQ", chan, myData];
// Encryption // Encryption
var keyStr = parsed.hashData.key; var keyStr = parsed.hashData.key;
@ -218,12 +210,10 @@ define([
// Send encrypted message // Send encrypted message
if (pendingRequests.indexOf(netfluxId) === -1) { if (pendingRequests.indexOf(netfluxId) === -1) {
pendingRequests.push(netfluxId); pendingRequests.push(netfluxId);
var proxy = common.getProxy(); cfg.updateMetadata(); // redraws the userlist in pad
// this redraws the userlist after a change has occurred
// TODO rename this function to reflect its purpose
common.changeDisplayName(proxy[Constants.displayNameKey]);
} }
network.sendto(netfluxId, msgStr); network.sendto(netfluxId, msgStr);
cb();
}; };
return Msg; return Msg;

View file

@ -1,11 +1,11 @@
define([ define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/common/curve.js', '/common/curve.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-util.js', '/common/common-util.js',
'/common/common-realtime.js', '/common/common-realtime.js',
], function ($, Crypto, Curve, Hash, Util, Realtime) { '/common/common-constants.js',
], function (Crypto, Curve, Hash, Util, Realtime, Constants) {
'use strict'; 'use strict';
var Msg = { var Msg = {
inputs: [], inputs: [],
@ -28,7 +28,7 @@ define([
var createData = Msg.createData = function (proxy, hash) { var createData = Msg.createData = function (proxy, hash) {
return { return {
channel: hash || Hash.createChannelId(), channel: hash || Hash.createChannelId(),
displayName: proxy['cryptpad.username'], displayName: proxy[Constants.displayNameKey],
profile: proxy.profile && proxy.profile.view, profile: proxy.profile && proxy.profile.view,
edPublic: proxy.edPublic, edPublic: proxy.edPublic,
curvePublic: proxy.curvePublic, curvePublic: proxy.curvePublic,
@ -56,7 +56,7 @@ define([
}); });
}; };
Msg.messenger = function (common) { Msg.messenger = function (store) {
var messenger = { var messenger = {
handlers: { handlers: {
message: [], message: [],
@ -89,9 +89,9 @@ define([
var joining = {}; var joining = {};
// declare common variables // declare common variables
var network = common.getNetwork(); var network = store.network;
var proxy = common.getProxy(); var proxy = store.proxy;
var realtime = common.getRealtime(); var realtime = store.realtime;
Msg.hk = network.historyKeeper; Msg.hk = network.historyKeeper;
var friends = getFriendList(proxy); var friends = getFriendList(proxy);
@ -484,7 +484,7 @@ define([
}; };
var msg = ['GET_HISTORY', chan.id, cfg]; var msg = ['GET_HISTORY', chan.id, cfg];
network.sendto(network.historyKeeper, JSON.stringify(msg)) network.sendto(network.historyKeeper, JSON.stringify(msg))
.then($.noop, function (err) { .then(function () {}, function (err) {
throw new Error(err); throw new Error(err);
}); });
}; };
@ -629,14 +629,7 @@ define([
messenger.getMyInfo = function (cb) { messenger.getMyInfo = function (cb) {
cb(void 0, { cb(void 0, {
curvePublic: proxy.curvePublic, curvePublic: proxy.curvePublic,
displayName: common.getDisplayName(), displayName: proxy[Constants.displayNameKey]
});
};
messenger.clearOwnedChannel = function (channel, cb) {
common.clearOwnedChannel(channel, function (e) {
if (e) { return void cb(e); }
cb();
}); });
}; };

View file

@ -1,18 +1,6 @@
define([ define([], function () {
'/customize/application_config.js',
'/customize/messages.js',
'/common/common-interface.js',
], function (AppConfig, Messages, UI) {
var common = {}; var common = {};
common.infiniteSpinnerDetected = false;
var BAD_STATE_TIMEOUT = typeof(AppConfig.badStateTimeout) === 'number'?
AppConfig.badStateTimeout: 30000;
var connected = false;
var intr;
var infiniteSpinnerHandlers = [];
/* /*
TODO make this not blow up when disconnected or lagging... TODO make this not blow up when disconnected or lagging...
*/ */
@ -20,7 +8,7 @@ define([
if (typeof(realtime.getAuthDoc) !== 'function') { if (typeof(realtime.getAuthDoc) !== 'function') {
return void console.error('improper use of this function'); return void console.error('improper use of this function');
} }
window.setTimeout(function () { setTimeout(function () {
if (realtime.getAuthDoc() === realtime.getUserDoc()) { if (realtime.getAuthDoc() === realtime.getUserDoc()) {
return void cb(); return void cb();
} else { } else {
@ -29,36 +17,5 @@ define([
}, 0); }, 0);
}; };
common.beginDetectingInfiniteSpinner = function (realtime) {
if (intr) { return; }
intr = window.setInterval(function () {
var l;
try {
l = realtime.getLag();
} catch (e) {
throw new Error("ChainPad.getLag() does not exist, please `bower update`");
}
if (l.lag < BAD_STATE_TIMEOUT || !connected) { return; }
realtime.abort();
// don't launch more than one popup
if (common.infiniteSpinnerDetected) { return; }
infiniteSpinnerHandlers.forEach(function (ish) { ish(); });
// inform the user their session is in a bad state
UI.confirm(Messages.realtime_unrecoverableError, function (yes) {
if (!yes) { return; }
window.parent.location.reload();
});
common.infiniteSpinnerDetected = true;
}, 2000);
};
common.onInfiniteSpinner = function (f) { infiniteSpinnerHandlers.push(f); };
common.setConnectionState = function (bool) {
if (typeof(bool) !== 'boolean') { return; }
connected = bool;
};
return common; return common;
}); });

View file

@ -98,7 +98,13 @@ define([
target: data.target target: data.target
}; };
if (data.filter && !data.filter(file)) { if (data.filter && !data.filter(file)) {
UI.log('Invalid avatar (type or size)'); return;
}
if (data.transformer) {
data.transformer(file, function (newFile) {
data.FM.handleFile(newFile, ev);
if (callback) { callback(); }
});
return; return;
} }
data.FM.handleFile(file, ev); data.FM.handleFile(file, ev);
@ -571,12 +577,7 @@ define([
// getPinnedUsage updates common.account.usage, and other values // getPinnedUsage updates common.account.usage, and other values
// so we can just use those and only check for errors // so we can just use those and only check for errors
var $container = $('<span>', {'class':'cp-limit-container'}); var $container = $('<span>', {'class':'cp-limit-container'});
var todo; var todo = function (err, data) {
var updateUsage = Util.notAgainForAnother(function () {
common.getPinUsage(todo);
}, LIMIT_REFRESH_RATE);
todo = function (err, data) {
if (err) { return void console.error(err); } if (err) { return void console.error(err); }
var usage = data.usage; var usage = data.usage;
@ -645,6 +646,10 @@ define([
$limit.append($usage).append($text); $limit.append($usage).append($text);
}; };
var updateUsage = Util.notAgainForAnother(function () {
common.getPinUsage(todo);
}, LIMIT_REFRESH_RATE);
setInterval(function () { setInterval(function () {
updateUsage(); updateUsage();
}, LIMIT_REFRESH_RATE * 3); }, LIMIT_REFRESH_RATE * 3);

View file

@ -209,6 +209,43 @@ define([], function () {
xhr.send(); xhr.send();
}; };
// Check if an element is a plain object
Util.isObject = function (o) {
return typeof (o) === "object" &&
Object.prototype.toString.call(o) === '[object Object]';
};
Util.isCircular = function (o) {
try {
JSON.stringify(o);
return false;
} catch (e) { return true; }
};
/* recursively adds the properties of an object 'b' to 'a'
arrays are only shallow copies, so references to the original
might still be present. Be mindful if you will modify 'a' in the future */
Util.extend = function (a, b) {
if (!Util.isObject(a) || !Util.isObject(b)) {
return void console.log("Extend only works with 2 objects");
}
if (Util.isCircular(b)) {
return void console.log("Extend doesn't accept circular objects");
}
for (var k in b) {
if (Util.isObject(b[k])) {
a[k] = {};
Util.extend(a[k], b[k]);
continue;
}
if (Array.isArray(b[k])) {
a[k] = b[k].slice();
continue;
}
a[k] = b[k];
}
};
return Util; return Util;
}); });
}(self)); }(self));

View file

@ -73,12 +73,12 @@ define([
realtime.contentUpdate(doc); realtime.contentUpdate(doc);
var to = window.setTimeout(function () { var to = setTimeout(function () {
cb(new Error("Timeout")); cb(new Error("Timeout"));
}, 5000); }, 5000);
Realtime.whenRealtimeSyncs(realtime, function () { Realtime.whenRealtimeSyncs(realtime, function () {
window.clearTimeout(to); clearTimeout(to);
realtime.abort(); realtime.abort();
finish(Session, void 0); finish(Session, void 0);
}); });

File diff suppressed because it is too large Load diff

10
www/common/dom-ready.js Normal file
View file

@ -0,0 +1,10 @@
define(function () {
return {
onReady: function (cb) {
if (document.readyState === 'complete') { return void cb(); }
document.onreadystatechange = function () {
if (document.readyState === 'complete') { cb(); }
};
}
};
});

View file

@ -33,7 +33,7 @@ define([], function () {
data.map[id] = el.textContent; data.map[id] = el.textContent;
return id; return id;
} }
if (!el || !el.attributes) { return void console.error(el); } if (!el || !el.attributes) { return; }
id = uid(); id = uid();
data.map[id] = [ data.map[id] = [
el.tagName, el.tagName,

View file

@ -1,359 +0,0 @@
define([
'jquery',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
'/common/userObject.js',
'/common/common-interface.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-constants.js',
'/common/migrate-user-object.js',
'/bower_components/chainpad/chainpad.dist.js',
'/common/outer/network-config.js',
'/common/outer/local-store.js',
], function ($, Listmap, Crypto, FO, UI, Hash, Util, Constants, Migrate, ChainPad, NetConfig,
LocalStore) {
/*
This module uses localStorage, which is synchronous, but exposes an
asyncronous API. This is so that we can substitute other storage
methods.
To override these methods, create another file at:
/customize/storage.js
*/
var Store = {};
var store;
var initStore = function (filesOp, storeObj, exp) {
var ret = {};
var safeSet = function (key, val) {
storeObj[key] = val;
};
// Store uses nodebacks...
ret.set = function (key, val, cb) {
safeSet(key, val);
cb();
};
// implement in alternative store
ret.setBatch = function (map, cb) {
Object.keys(map).forEach(function (key) {
safeSet(key, map[key]);
});
cb(void 0, map);
};
ret.setDrive = function (key, val, cb) {
storeObj.drive[key] = val;
cb();
};
var safeGet = function (key) {
return storeObj[key];
};
ret.get = function (key, cb) {
cb(void 0, safeGet(key));
};
// implement in alternative store
ret.getBatch = function (keys, cb) {
var res = {};
keys.forEach(function (key) {
res[key] = safeGet(key);
});
cb(void 0, res);
};
var getAttributeObject = function (attr) {
if (typeof attr === "string") {
console.error('DEPRECATED: use setAttribute with an array, not a string');
return {
obj: storeObj.settings,
key: attr
};
}
if (!Array.isArray(attr)) { throw new Error("Attribute must be string or array"); }
if (attr.length === 0) { throw new Error("Attribute can't be empty"); }
var obj = storeObj.settings;
attr.forEach(function (el, i) {
if (i === attr.length-1) { return; }
if (!obj[el]) {
obj[el] = {};
}
else if (typeof obj[el] !== "object") { throw new Error("Wrong attribute"); }
obj = obj[el];
});
return {
obj: obj,
key: attr[attr.length-1]
};
};
ret.setAttribute = function (attr, value, cb) {
try {
var object = getAttributeObject(attr);
object.obj[object.key] = value;
} catch (e) { return void cb(e); }
cb();
};
ret.getAttribute = function (attr, cb) {
var object;
try {
object = getAttributeObject(attr);
} catch (e) { return void cb(e); }
cb(null, object.obj[object.key]);
};
ret.setPadAttribute = filesOp.setPadAttribute;
ret.getPadAttribute = filesOp.getPadAttribute;
ret.getIdFromHref = filesOp.getIdFromHref;
ret.getDrive = function (key, cb) {
cb(void 0, storeObj.drive[key]);
};
var safeRemove = function (key) {
delete storeObj[key];
};
ret.remove = function (key, cb) {
safeRemove(key);
cb();
};
// implement in alternative store
ret.removeBatch = function (keys, cb) {
keys.forEach(function (key) {
safeRemove(key);
});
cb();
};
ret.keys = function (cb) {
cb(void 0, Object.keys(storeObj));
};
ret.removeData = filesOp.removeData;
ret.pushData = filesOp.pushData;
ret.addPad = filesOp.add;
ret.forgetPad = function (href, cb) {
filesOp.forget(href);
cb();
};
ret.listTemplates = function () {
var templateFiles = filesOp.getFiles(['template']);
var res = [];
templateFiles.forEach(function (f) {
var data = filesOp.getFileData(f);
res.push(JSON.parse(JSON.stringify(data)));
});
return res;
};
ret.getProxy = function () {
return exp;
};
ret.getLoginName = function () {
return storeObj.login_name;
};
ret.repairDrive = function () {
filesOp.fixFiles();
};
ret.getEmptyObject = function () {
return filesOp.getStructure();
};
ret.replace = filesOp.replace;
ret.restoreHref = filesOp.restoreHref;
ret.changeHandlers = [];
ret.change = function () {};
ret.getProfile = function () {
return storeObj.profile;
};
return ret;
};
var tryParsing = function (x) {
try { return JSON.parse(x); }
catch (e) {
console.error(e);
return null;
}
};
var onReady = function (f, proxy, Cryptpad, exp) {
var fo = exp.fo = FO.init(proxy.drive, {
Cryptpad: Cryptpad,
loggedIn: LocalStore.isLoggedIn()
});
var todo = function () {
fo.fixFiles();
Migrate(proxy, Cryptpad);
store = initStore(fo, proxy, exp);
if (typeof(f) === 'function') {
f(void 0, store);
}
//storeObj = proxy;
var requestLogin = function () {
// log out so that you don't go into an endless loop...
LocalStore.logout();
// redirect them to log in, and come back when they're done.
sessionStorage.redirectTo = window.location.href;
window.location.href = '/login/';
};
var tokenKey = 'loginToken';
if (LocalStore.isLoggedIn()) {
/* This isn't truly secure, since anyone who can read the user's object can
set their local loginToken to match that in the object. However, it exposes
a UI that will work most of the time. */
// every user object should have a persistent, random number
if (typeof(proxy.loginToken) !== 'number') {
proxy[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
}
// copy User_hash into sessionStorage because cross-domain iframes
// on safari replaces localStorage with sessionStorage or something
if (sessionStorage) { sessionStorage.setItem('User_hash', localStorage.getItem('User_hash')); }
var localToken = tryParsing(localStorage.getItem(tokenKey));
if (localToken === null) {
// if that number hasn't been set to localStorage, do so.
localStorage.setItem(tokenKey, proxy.loginToken);
} else if (localToken !== proxy[tokenKey]) {
// if it has been, and the local number doesn't match that in
// the user object, request that they reauthenticate.
return void requestLogin();
}
}
if (!proxy.settings || !proxy.settings.general ||
typeof(proxy.settings.general.allowUserFeedback) !== 'boolean') {
proxy.settings = proxy.settings || {};
proxy.settings.general = proxy.settings.general || {};
proxy.settings.general.allowUserFeedback = true;
}
if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) {
// even anonymous users should have a persistent, unique-ish id
console.log('generating a persistent identifier');
proxy.uid = Hash.createChannelId();
}
// if the user is logged in, but does not have signing keys...
if (LocalStore.isLoggedIn() && (!Cryptpad.hasSigningKeys(proxy) ||
!Cryptpad.hasCurveKeys(proxy))) {
return void requestLogin();
}
proxy.on('change', [Constants.displayNameKey], function (o, n) {
if (typeof(n) !== "string") { return; }
Cryptpad.changeDisplayName(n);
});
proxy.on('change', ['profile'], function () {
// Trigger userlist update when the avatar has changed
Cryptpad.changeDisplayName(proxy[Constants.displayNameKey]);
});
proxy.on('change', ['friends'], function () {
// Trigger userlist update when the avatar has changed
Cryptpad.changeDisplayName(proxy[Constants.displayNameKey]);
});
proxy.on('change', [tokenKey], function () {
var localToken = tryParsing(localStorage.getItem(tokenKey));
if (localToken !== proxy[tokenKey]) {
return void requestLogin();
}
});
};
fo.migrate(todo);
};
var initialized = false;
var init = function (f, Cryptpad) {
if (!Cryptpad || initialized) { return; }
initialized = true;
var hash = LocalStore.getUserHash() || LocalStore.getFSHash() || Hash.createRandomHash();
if (!hash) {
throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
}
var secret = Hash.getSecrets('drive', hash);
var listmapConfig = {
data: {},
websocketURL: NetConfig.getWebsocketURL(),
channel: secret.channel,
readOnly: false,
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
userName: 'fs',
logLevel: 1,
ChainPad: ChainPad,
classic: true,
};
var exp = {};
var rt = window.rt = Listmap.create(listmapConfig);
exp.realtime = rt.realtime;
exp.proxy = rt.proxy;
rt.proxy.on('create', function (info) {
exp.info = info;
if (!LocalStore.getUserHash()) {
LocalStore.setFSHash(Hash.getEditHashFromKeys(info.channel, secret.keys));
}
}).on('ready', function () {
if (store) { return; } // the store is already ready, it is a reconnection
if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
var drive = rt.proxy.drive;
// Creating a new anon drive: import anon pads from localStorage
if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey]))
&& !drive['filesData']) {
drive[Constants.oldStorageKey] = [];
onReady(f, rt.proxy, Cryptpad, exp);
return;
}
// Drive already exist: return the existing drive, don't load data from legacy store
onReady(f, rt.proxy, Cryptpad, exp);
})
.on('change', ['drive', 'migrate'], function () {
var path = arguments[2];
var value = arguments[1];
if (path[0] === 'drive' && path[1] === "migrate" && value === 1) {
rt.network.disconnect();
rt.realtime.abort();
UI.alert(Cryptpad.Messages.fs_migration, null, true);
}
});
};
Store.ready = function (f, Cryptpad) {
if (store) { // Store.ready probably called twice, store already ready
if (typeof(f) === 'function') {
f(void 0, store);
}
} else {
init(f, Cryptpad);
}
};
return Store;
});

View file

@ -2,8 +2,8 @@ define([
'/common/cryptget.js', '/common/cryptget.js',
'/common/userObject.js', '/common/userObject.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/outer/local-store.js', '/common/common-realtime.js',
], function (Crypt, FO, Hash, LocalStore) { ], function (Crypt, FO, Hash, Realtime) {
var exp = {}; var exp = {};
var getType = function (el) { var getType = function (el) {
@ -86,7 +86,7 @@ define([
exp.anonDriveIntoUser = function (proxyData, fsHash, cb) { exp.anonDriveIntoUser = function (proxyData, fsHash, cb) {
// Make sure we have an FS_hash and we don't use it, otherwise just stop the migration and cb // Make sure we have an FS_hash and we don't use it, otherwise just stop the migration and cb
if (!fsHash || !LocalStore.isLoggedIn()) { if (!fsHash || !proxyData.loggedIn) {
if (typeof(cb) === "function") { return void cb(); } if (typeof(cb) === "function") { return void cb(); }
} }
// Get the content of FS_hash and then merge the objects, remove the migration key and cb // Get the content of FS_hash and then merge the objects, remove the migration key and cb
@ -105,11 +105,11 @@ define([
if (parsed) { if (parsed) {
var proxy = proxyData.proxy; var proxy = proxyData.proxy;
var oldFo = FO.init(parsed.drive, { var oldFo = FO.init(parsed.drive, {
loggedIn: LocalStore.isLoggedIn() loggedIn: proxyData.loggedIn
}); });
var onMigrated = function () { var onMigrated = function () {
oldFo.fixFiles(); oldFo.fixFiles();
var newFo = proxyData.fo; var newFo = proxyData.userObject;
var oldRecentPads = parsed.drive[newFo.FILES_DATA]; var oldRecentPads = parsed.drive[newFo.FILES_DATA];
var newRecentPads = proxy.drive[newFo.FILES_DATA]; var newRecentPads = proxy.drive[newFo.FILES_DATA];
var oldFiles = oldFo.getFiles([newFo.FILES_DATA]); var oldFiles = oldFo.getFiles([newFo.FILES_DATA]);
@ -154,7 +154,9 @@ define([
proxy.FS_hashes = []; proxy.FS_hashes = [];
} }
proxy.FS_hashes.push(fsHash); proxy.FS_hashes.push(fsHash);
if (typeof(cb) === "function") { cb(); } if (typeof(cb) === "function") {
Realtime.whenRealtimeSyncs(proxyData.realtime, cb);
}
}; };
oldFo.migrate(onMigrated); oldFo.migrate(onMigrated);
return; return;

View file

@ -59,7 +59,6 @@ define(['json.sortify'], function (Sortify) {
} }
if (metadataObj.title !== rememberedTitle) { if (metadataObj.title !== rememberedTitle) {
console.log("Title update\n" + metadataObj.title + '\n');
rememberedTitle = metadataObj.title; rememberedTitle = metadataObj.title;
titleChangeHandlers.forEach(function (f) { f(metadataObj.title); }); titleChangeHandlers.forEach(function (f) { f(metadataObj.title); });
} }
@ -73,30 +72,45 @@ define(['json.sortify'], function (Sortify) {
}); });
}; };
var netfluxId;
var isReady = false;
var readyHandlers = [];
sframeChan.on('EV_METADATA_UPDATE', function (ev) { sframeChan.on('EV_METADATA_UPDATE', function (ev) {
meta = ev; meta = ev;
if (ev.priv) { if (ev.priv) {
priv = ev.priv; priv = ev.priv;
} }
if (netfluxId) {
meta.user.netfluxId = netfluxId;
}
if (!isReady) {
isReady = true;
readyHandlers.forEach(function (f) { f(); });
}
change(true); change(true);
}); });
sframeChan.on('EV_RT_CONNECT', function (ev) { sframeChan.on('EV_RT_CONNECT', function (ev) {
meta.user.netfluxId = ev.myID; netfluxId = ev.myID;
members = ev.members; members = ev.members;
if (!meta.user) { return; }
meta.user.netfluxId = netfluxId;
change(true); change(true);
}); });
sframeChan.on('EV_RT_JOIN', function (ev) { sframeChan.on('EV_RT_JOIN', function (ev) {
members.push(ev); members.push(ev);
if (!meta.user) { return; }
change(false); change(false);
}); });
sframeChan.on('EV_RT_LEAVE', function (ev) { sframeChan.on('EV_RT_LEAVE', function (ev) {
var idx = members.indexOf(ev); var idx = members.indexOf(ev);
if (idx === -1) { console.log('Error: ' + ev + ' not in members'); return; } if (idx === -1) { console.log('Error: ' + ev + ' not in members'); return; }
members.splice(idx, 1); members.splice(idx, 1);
if (!meta.user) { return; }
change(false); change(false);
}); });
sframeChan.on('EV_RT_DISCONNECT', function () { sframeChan.on('EV_RT_DISCONNECT', function () {
members = []; members = [];
if (!meta.user) { return; }
change(true); change(true);
}); });
@ -140,6 +154,10 @@ define(['json.sortify'], function (Sortify) {
}, },
getNetfluxId : function () { getNetfluxId : function () {
return meta.user.netfluxId; return meta.user.netfluxId;
},
onReady: function (f) {
if (isReady) { return void f(); }
readyHandlers.push(f);
} }
}); });
}; };

View file

@ -0,0 +1,924 @@
define([
'/common/userObject.js',
'/common/migrate-user-object.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-constants.js',
'/common/common-feedback.js',
'/common/common-realtime.js',
'/common/common-messaging.js',
'/common/common-messenger.js',
'/common/outer/network-config.js',
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
'/bower_components/chainpad/chainpad.dist.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
], function (UserObject, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger,
NetConfig,
Crypto, ChainPad, Listmap) {
var Store = {};
var postMessage = function () {};
var storeHash;
var store = {};
var onSync = function (cb) {
Realtime.whenRealtimeSyncs(store.realtime, cb);
};
Store.get = function (key, cb) {
cb(Util.find(store.proxy, key));
};
Store.set = function (data, cb) {
var path = data.key.slice();
var key = path.pop();
var obj = Util.find(store.proxy, path);
if (!obj || typeof(obj) !== "object") { return void cb({error: 'INVALID_PATH'}); }
if (typeof data.value === "undefined") {
delete obj[key];
} else {
obj[key] = data.value;
}
onSync(cb);
};
Store.hasSigningKeys = function () {
if (!store.proxy) { return; }
return typeof(store.proxy.edPrivate) === 'string' &&
typeof(store.proxy.edPublic) === 'string';
};
Store.hasCurveKeys = function () {
if (!store.proxy) { return; }
return typeof(store.proxy.curvePrivate) === 'string' &&
typeof(store.proxy.curvePublic) === 'string';
};
var getUserChannelList = function () {
// start with your userHash...
var userHash = storeHash;
if (!userHash) { return null; }
var userParsedHash = Hash.parseTypeHash('drive', userHash);
var userChannel = userParsedHash && userParsedHash.channel;
if (!userChannel) { return null; }
var list = store.userObject.getFiles([store.userObject.FILES_DATA]).map(function (id) {
return Hash.hrefToHexChannelId(store.userObject.getFileData(id).href);
})
.filter(function (x) { return x; });
// Get the avatar
var profile = store.proxy.profile;
if (profile) {
var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit) : null;
if (profileChan) { list.push(profileChan); }
var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar) : null;
if (avatarChan) { list.push(avatarChan); }
}
if (store.proxy.friends) {
var fList = Messaging.getFriendChannelsList(store.proxy);
list = list.concat(fList);
}
list.push(Util.base64ToHex(userChannel));
list.sort();
return list;
};
var getCanonicalChannelList = function () {
return Util.deduplicateString(getUserChannelList()).sort();
};
//////////////////////////////////////////////////////////////////
/////////////////////// RPC //////////////////////////////////////
//////////////////////////////////////////////////////////////////
Store.pinPads = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
if (typeof(cb) !== 'function') {
console.error('expected a callback');
}
store.rpc.pin(data, function (e, hash) {
if (e) { return void cb({error: e}); }
cb({hash: hash});
});
};
Store.unpinPads = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.unpin(data, function (e, hash) {
if (e) { return void cb({error: e}); }
cb({hash: hash});
});
};
var account = {};
Store.getPinnedUsage = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.getFileListSize(function (err, bytes) {
if (typeof(bytes) === 'number') {
account.usage = bytes;
}
cb({bytes: bytes});
});
};
// Update for all users from accounts and return current user limits
Store.updatePinLimit = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.updatePinLimits(function (e, limit, plan, note) {
if (e) { return void cb({error: e}); }
account.limit = limit;
account.plan = plan;
account.note = note;
cb(account);
});
};
// Get current user limits
Store.getPinLimit = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
var ALWAYS_REVALIDATE = true;
if (ALWAYS_REVALIDATE || typeof(account.limit) !== 'number' ||
typeof(account.plan) !== 'string' ||
typeof(account.note) !== 'string') {
return void store.rpc.getLimit(function (e, limit, plan, note) {
if (e) { return void cb({error: e}); }
account.limit = limit;
account.plan = plan;
account.note = note;
cb(account);
});
}
cb(account);
};
Store.clearOwnedChannel = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.clearOwnedChannel(data, function (err) {
cb({error:err});
});
};
Store.uploadComplete = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.uploadComplete(function (err, res) {
if (err) { return void cb({error:err}); }
cb(res);
});
};
Store.uploadStatus = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.uploadStatus(data.size, function (err, res) {
if (err) { return void cb({error:err}); }
cb(res);
});
};
Store.uploadCancel = function (data, cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
store.rpc.uploadCancel(function (err, res) {
if (err) { return void cb({error:err}); }
cb(res);
});
};
var arePinsSynced = function (cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
var list = getCanonicalChannelList();
var local = Hash.hashChannelList(list);
store.rpc.getServerHash(function (e, hash) {
if (e) { return void cb(e); }
cb(null, hash === local);
});
};
var resetPins = function (cb) {
if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
var list = getCanonicalChannelList();
store.rpc.reset(list, function (e, hash) {
if (e) { return void cb(e); }
cb(null, hash);
});
};
Store.uploadChunk = function (data, cb) {
store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) {
cb({
error: e,
msg: msg
});
});
};
Store.initRpc = function (data, cb) {
require(['/common/pinpad.js'], function (Pinpad) {
Pinpad.create(store.network, store.proxy, function (e, call) {
if (e) { return void cb({error: e}); }
store.rpc = call;
Store.getPinLimit(null, function (obj) {
if (obj.error) { console.error(obj.error); }
account.limit = obj.limit;
account.plan = obj.plan;
account.note = obj.note;
cb(obj);
});
arePinsSynced(function (err, yes) {
if (!yes) {
resetPins(function (err) {
if (err) { return console.error(err); }
console.log('RESET DONE');
});
}
});
});
});
};
//////////////////////////////////////////////////////////////////
////////////////// ANON RPC //////////////////////////////////////
//////////////////////////////////////////////////////////////////
Store.anonRpcMsg = function (data, cb) {
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
store.anon_rpc.send(data.msg, data.data, function (err, res) {
if (err) { return void cb({error: err}); }
cb(res);
});
};
Store.getFileSize = function (data, cb) {
console.log(data, cb);
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
var channelId = Hash.hrefToHexChannelId(data.href);
store.anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) {
if (e) { return void cb({error: e}); }
if (response && response.length && typeof(response[0]) === 'number') {
return void cb({size: response[0]});
} else {
cb({error: 'INVALID_RESPONSE'});
}
});
};
Store.getMultipleFileSize = function (data, cb) {
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
if (!Array.isArray(data.files)) {
return void cb({error: 'INVALID_FILE_LIST'});
}
store.anon_rpc.send('GET_MULTIPLE_FILE_SIZE', data.files, function (e, res) {
if (e) { return void cb({error: e}); }
if (res && res.length && typeof(res[0]) === 'object') {
cb({size: res[0]});
} else {
cb({error: 'UNEXPECTED_RESPONSE'});
}
});
};
Store.initAnonRpc = function (data, cb) {
require([
'/common/rpc.js',
], function (Rpc) {
Rpc.createAnonymous(store.network, function (e, call) {
if (e) { return void cb({error: e}); }
store.anon_rpc = call;
cb();
});
});
};
//////////////////////////////////////////////////////////////////
/////////////////////// Store ////////////////////////////////////
//////////////////////////////////////////////////////////////////
// Get the metadata for sframe-common-outer
Store.getMetadata = function (data, cb) {
var metadata = {
// "user" is shared with everybody via the userlist
user: {
name: store.proxy[Constants.displayNameKey],
uid: store.proxy.uid,
avatar: Util.find(store.proxy, ['profile', 'avatar']),
profile: Util.find(store.proxy, ['profile', 'view']),
curvePublic: store.proxy.curvePublic,
},
// "priv" is not shared with other users but is needed by the apps
priv: {
edPublic: store.proxy.edPublic,
friends: store.proxy.friends,
settings: store.proxy.settings,
thumbnails: !Util.find(store.proxy, ['settings', 'general', 'disableThumbnails'])
}
};
cb(JSON.parse(JSON.stringify(metadata)));
};
var makePad = function (href, title) {
var now = +new Date();
return {
href: href,
atime: now,
ctime: now,
title: title || Hash.getDefaultName(Hash.parsePadUrl(href)),
};
};
Store.addPad = function (data, cb) {
if (!data.href) { return void cb({error:'NO_HREF'}); }
var pad = makePad(data.href, data.title);
store.userObject.pushData(pad, function (e, id) {
if (e) { return void cb({error: "Error while adding a template:"+ e}); }
var path = data.path || ['root'];
store.userObject.add(id, path);
onSync(cb);
});
};
/**
* add a "What is CryptPad?" pad in the drive
* data
* - driveReadme
* - driveReadmeTitle
*/
Store.createReadme = function (data, cb) {
require(['/common/cryptget.js'], function (Crypt) {
var hash = Hash.createRandomHash();
Crypt.put(hash, data.driveReadme, function (e) {
if (e) {
return void cb({ error: "Error while creating the default pad:"+ e});
}
var href = '/pad/#' + hash;
var fileData = {
href: href,
title: data.driveReadmeTitle,
atime: +new Date(),
ctime: +new Date()
};
store.userObject.pushData(fileData, function (e, id) {
if (e) {
return void cb({ error: "Error while creating the default pad:"+ e});
}
store.userObject.add(id);
onSync(cb);
});
});
});
};
/**
* Merge the anonymous drive into the user drive at registration
* data
* - anonHash
*/
Store.migrateAnonDrive = function (data, cb) {
require(['/common/mergeDrive.js'], function (Merge) {
var hash = data.anonHash;
Merge.anonDriveIntoUser(store, hash, cb);
});
};
var getAttributeObject = function (attr) {
if (typeof attr === "string") {
console.error('DEPRECATED: use setAttribute with an array, not a string');
return {
obj: store.proxy.settings,
key: attr
};
}
if (!Array.isArray(attr)) { return void console.error("Attribute must be string or array"); }
if (attr.length === 0) { return void console.error("Attribute can't be empty"); }
var obj = store.proxy.settings;
attr.forEach(function (el, i) {
if (i === attr.length-1) { return; }
if (!obj[el]) {
obj[el] = {};
}
else if (typeof obj[el] !== "object") { return void console.error("Wrong attribute"); }
obj = obj[el];
});
return {
obj: obj,
key: attr[attr.length-1]
};
};
// Set the display name (username) in the proxy
Store.setDisplayName = function (value, cb) {
store.proxy[Constants.displayNameKey] = value;
onSync(cb);
};
// Reset the drive part of the userObject (from settings)
Store.resetDrive = function (data, cb) {
store.proxy.drive = store.fo.getStructure();
onSync(cb);
};
/**
* Settings & pad attributes
* data
* - href (String)
* - attr (Array)
* - value (String)
*/
Store.setPadAttribute = function (data, cb) {
store.userObject.setPadAttribute(data.href, data.attr, data.value, function () {
onSync(cb);
});
};
Store.getPadAttribute = function (data, cb) {
store.userObject.getPadAttribute(data.href, data.attr, function (err, val) {
if (err) { return void cb({error: err}); }
cb(val);
});
};
Store.setAttribute = function (data, cb) {
try {
var object = getAttributeObject(data.attr);
object.obj[object.key] = data.value;
} catch (e) { return void cb({error: e}); }
onSync(cb);
};
Store.getAttribute = function (data, cb) {
var object;
try {
object = getAttributeObject(data.attr);
} catch (e) { return void cb({error: e}); }
cb(object.obj[object.key]);
};
// Tags
Store.listAllTags = function (data, cb) {
var all = [];
var files = Util.find(store.proxy, ['drive', 'filesData']);
if (typeof(files) !== 'object') { return cb({error: 'invalid_drive'}); }
Object.keys(files).forEach(function (k) {
var file = files[k];
if (!Array.isArray(file.tags)) { return; }
file.tags.forEach(function (tag) {
if (all.indexOf(tag) === -1) { all.push(tag); }
});
});
cb(all);
};
// Templates
Store.getTemplates = function (data, cb) {
var templateFiles = store.userObject.getFiles(['template']);
var res = [];
templateFiles.forEach(function (f) {
var data = store.userObject.getFileData(f);
res.push(JSON.parse(JSON.stringify(data)));
});
cb(res);
};
// Pads
Store.moveToTrash = function (data, cb) {
var href = Hash.getRelativeHref(data.href);
store.userObject.forget(href);
onSync(cb);
};
Store.setPadTitle = function (data, cb) {
var title = data.title;
var href = data.href;
var p = Hash.parsePadUrl(href);
var h = p.hashData;
var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {};
var isStronger;
// If we don't find the new channel in our existing pads, we'll have to add the pads
// to filesData
var contains;
// Update all pads that use the same channel but with a weaker hash
// Edit > Edit (present) > View > View (present)
for (var id in allPads) {
var pad = allPads[id];
if (!pad.href) { continue; }
var p2 = Hash.parsePadUrl(pad.href);
var h2 = p2.hashData;
// Different types, proceed to the next one
// No hash data: corrupted pad?
if (p.type !== p2.type || !h2) { continue; }
var shouldUpdate = p.hash.replace(/\/$/, '') === p2.hash.replace(/\/$/, '');
// If the hash is different but represents the same channel, check if weaker or stronger
if (!shouldUpdate &&
h.version === 1 && h2.version === 1 &&
h.channel === h2.channel) {
// We had view & now we have edit, update
if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; }
// Same mode and we had present URL, update
else if (h.mode === h2.mode && h2.present) { shouldUpdate = true; }
// If we're here it means we have a weaker URL:
// update the date but keep the existing hash
else {
pad.atime = +new Date();
contains = true;
continue;
}
}
if (shouldUpdate) {
contains = true;
pad.atime = +new Date();
pad.title = title;
// If the href is different, it means we have a stronger one
if (href !== pad.href) { isStronger = true; }
pad.href = href;
}
}
if (isStronger) {
// If we have a stronger url, remove the possible weaker from the trash.
// If all of the weaker ones were in the trash, add the stronger to ROOT
store.userObject.restoreHref(href);
}
// Add the pad if it does not exist in our drive
if (!contains) {
Store.addPad({
href: href,
title: title,
path: data.path || (store.data && store.data.initialPath)
}, cb);
return;
}
onSync(cb);
};
// Filepicker app
Store.getSecureFilesList = function (query, cb) {
var list = {};
var hashes = [];
var types = query.types;
var where = query.where;
var filter = query.filter || {};
var isFiltered = function (type, data) {
var filtered;
var fType = filter.fileType || [];
if (type === 'file' && fType.length) {
if (!data.fileType) { return true; }
filtered = !fType.some(function (t) {
return data.fileType.indexOf(t) === 0;
});
}
return filtered;
};
store.userObject.getFiles(where).forEach(function (id) {
var data = store.userObject.getFileData(id);
var parsed = Hash.parsePadUrl(data.href);
if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) &&
hashes.indexOf(parsed.hash) === -1 &&
!isFiltered(parsed.type, data)) {
hashes.push(parsed.hash);
list[id] = data;
}
});
cb(list);
};
// Messaging (manage friends from the userlist)
var getMessagingCfg = function () {
return {
proxy: store.proxy,
realtime: store.realtime,
network: store.network,
updateMetadata: function () {
postMessage("UPDATE_METADATA");
},
pinPads: Store.pinPads,
friendComplete: function (data, cb) {
postMessage("Q_FRIEND_COMPLETE", data, cb);
},
friendRequest: function (data) {
postMessage("EV_FRIEND_REQUEST", data);
},
};
};
Store.inviteFromUserlist = function (data, cb) {
var messagingCfg = getMessagingCfg();
Messaging.inviteFromUserlist(messagingCfg, data, cb);
};
// Messenger
// Get hashes for the share button
Store.getStrongerHash = function (data, cb) {
var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {};
// If we have a stronger version in drive, add it and add a redirect button
var stronger = Hash.findStronger(data.href, allPads);
if (stronger) {
var parsed2 = Hash.parsePadUrl(stronger);
return void cb(parsed2.hash);
}
cb();
};
Store.messenger = {
getFriendList: function (data, cb) {
store.messenger.getFriendList(function (e, keys) {
cb({
error: e,
data: keys,
});
});
},
getMyInfo: function (data, cb) {
store.messenger.getMyInfo(function (e, info) {
cb({
error: e,
data: info,
});
});
},
getFriendInfo: function (data, cb) {
store.messenger.getFriendInfo(data, function (e, info) {
cb({
error: e,
data: info,
});
});
},
removeFriend: function (data, cb) {
store.messenger.removeFriend(data, function (e, info) {
cb({
error: e,
data: info,
});
});
},
openFriendChannel: function (data, cb) {
store.messenger.openFriendChannel(data, function (e) {
cb({ error: e, });
});
},
getFriendStatus: function (data, cb) {
store.messenger.getStatus(data, function (e, online) {
cb({
error: e,
data: online,
});
});
},
getMoreHistory: function (data, cb) {
store.messenger.getMoreHistory(data.curvePublic, data.sig, data.count, function (e, history) {
cb({
error: e,
data: history,
});
});
},
sendMessage: function (data, cb) {
store.messenger.sendMessage(data.curvePublic, data.content, function (e) {
cb({
error: e,
});
});
},
setChannelHead: function (data, cb) {
store.messenger.setChannelHead(data.curvePublic, data.sig, function (e) {
cb({
error: e
});
});
}
};
var onReady = function (returned, cb) {
var proxy = store.proxy;
var userObject = store.userObject = UserObject.init(proxy.drive, {
pinPads: Store.pinPads,
loggedIn: store.loggedIn
});
var todo = function () {
userObject.fixFiles();
Migrate(proxy);
var requestLogin = function () {
postMessage("REQUEST_LOGIN");
};
if (store.loggedIn) {
/* This isn't truly secure, since anyone who can read the user's object can
set their local loginToken to match that in the object. However, it exposes
a UI that will work most of the time. */
// every user object should have a persistent, random number
if (typeof(proxy.loginToken) !== 'number') {
proxy[Constants.tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
}
returned[Constants.tokenKey] = proxy[Constants.tokenKey];
if (store.data.localToken && store.data.localToken !== proxy[Constants.tokenKey]) {
// the local number doesn't match that in
// the user object, request that they reauthenticate.
return void requestLogin();
}
}
if (!proxy.settings || !proxy.settings.general ||
typeof(proxy.settings.general.allowUserFeedback) !== 'boolean') {
proxy.settings = proxy.settings || {};
proxy.settings.general = proxy.settings.general || {};
proxy.settings.general.allowUserFeedback = true;
}
returned.feedback = proxy.settings.general.allowUserFeedback;
if (typeof(cb) === 'function') { cb(returned); }
if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) {
// even anonymous users should have a persistent, unique-ish id
console.log('generating a persistent identifier');
proxy.uid = Hash.createChannelId();
}
// if the user is logged in, but does not have signing keys...
if (store.loggedIn && (!Store.hasSigningKeys() ||
!Store.hasCurveKeys())) {
return void requestLogin();
}
proxy.on('change', [Constants.displayNameKey], function (o, n) {
if (typeof(n) !== "string") { return; }
postMessage("UPDATE_METADATA");
});
proxy.on('change', ['profile'], function () {
// Trigger userlist update when the avatar has changed
postMessage("UPDATE_METADATA");
});
proxy.on('change', ['friends'], function () {
// Trigger userlist update when the friendlist has changed
postMessage("UPDATE_METADATA");
});
proxy.on('change', ['settings'], function () {
postMessage("UPDATE_METADATA");
});
proxy.on('change', [Constants.tokenKey], function () {
postMessage("UPDATE_TOKEN", { token: proxy[Constants.tokenKey] });
});
};
userObject.migrate(todo);
};
var connect = function (data, cb) {
var hash = data.userHash || data.anonHash || Hash.createRandomHash();
storeHash = hash;
if (!hash) {
throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...');
}
var secret = Hash.getSecrets('drive', hash);
var listmapConfig = {
data: {},
websocketURL: NetConfig.getWebsocketURL(),
channel: secret.channel,
readOnly: false,
validateKey: secret.keys.validateKey || undefined,
crypto: Crypto.createEncryptor(secret.keys),
userName: 'fs',
logLevel: 1,
ChainPad: ChainPad,
classic: true,
};
var rt = Listmap.create(listmapConfig);
store.proxy = rt.proxy;
store.loggedIn = typeof(data.userHash) !== "undefined";
var returned = {};
rt.proxy.on('create', function (info) {
store.realtime = info.realtime;
store.network = info.network;
if (!data.userHash) {
returned.anonHash = Hash.getEditHashFromKeys(info.channel, secret.keys);
}
}).on('ready', function () {
if (store.userObject) { return; } // the store is already ready, it is a reconnection
if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; }
var drive = rt.proxy.drive;
// Creating a new anon drive: import anon pads from localStorage
if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey]))
&& !drive['filesData']) {
drive[Constants.oldStorageKey] = [];
}
// Drive already exist: return the existing drive, don't load data from legacy store
onReady(returned, cb);
})
.on('change', ['drive', 'migrate'], function () {
var path = arguments[2];
var value = arguments[1];
if (path[0] === 'drive' && path[1] === "migrate" && value === 1) {
rt.network.disconnect();
rt.realtime.abort();
}
});
};
/**
* Data:
* - userHash or anonHash
* Todo in cb
* - LocalStore.setFSHash if needed
* - sessionStorage.User_Hash
* - stuff with tokenKey
* Event to outer
* - requestLogin
*/
var initialized = false;
Store.init = function (data, callback) {
if (initialized) {
return void callback({
error: 'ALREADY_INIT'
});
}
initialized = true;
postMessage = function (cmd, d, cb) {
setTimeout(function () {
data.query(cmd, d, cb); // TODO temporary, will be replaced by webworker channel
});
};
store.data = data;
connect(data, function (ret) {
if (Object.keys(store.proxy).length === 1) {
Feedback.send("FIRST_APP_USE", true);
}
callback(ret);
var messagingCfg = getMessagingCfg();
Messaging.addDirectMessageHandler(messagingCfg);
if (data.messenger) {
var messenger = store.messenger = Messenger.messenger(store); // TODO
messenger.on('message', function (message) {
postMessage('CONTACTS_MESSAGE', message);
});
messenger.on('join', function (curvePublic, channel) {
postMessage('CONTACTS_JOIN', {
curvePublic: curvePublic,
channel: channel,
});
});
messenger.on('leave', function (curvePublic, channel) {
postMessage('CONTACTS_LEAVE', {
curvePublic: curvePublic,
channel: channel,
});
});
messenger.on('update', function (info, curvePublic) {
postMessage('CONTACTS_UPDATE', {
curvePublic: curvePublic,
info: info,
});
});
messenger.on('friend', function (curvePublic) {
postMessage('CONTACTS_FRIEND', {
curvePublic: curvePublic,
});
});
messenger.on('unfriend', function (curvePublic) {
postMessage('CONTACTS_UNFRIEND', {
curvePublic: curvePublic,
});
});
}
});
};
Store.disconnect = function () {
if (!store.network) { return; }
store.network.disconnect();
};
return Store;
});

View file

@ -0,0 +1,159 @@
define([
'/common/outer/async-store.js'
], function (Store) {
var Rpc = {};
Rpc.query = function (cmd, data, cb) {
switch (cmd) {
// READY
case 'CONNECT': {
Store.init(data, cb); break;
}
case 'DISCONNECT': {
Store.disconnect(data, cb); break;
}
case 'CREATE_README': {
Store.createReadme(data, cb); break;
}
case 'MIGRATE_ANON_DRIVE': {
Store.migrateAnonDrive(data, cb); break;
}
// RPC
case 'INIT_RPC': {
Store.initRpc(data, cb); break;
}
case 'UPDATE_PIN_LIMIT': {
Store.updatePinLimit(data, cb); break;
}
case 'GET_PIN_LIMIT': {
Store.getPinLimit(data, cb); break;
}
case 'CLEAR_OWNED_CHANNEL': {
Store.clearOwnedChannel(data, cb); break;
}
case 'UPLOAD_CHUNK': {
Store.uploadChunk(data, cb); break;
}
case 'UPLOAD_COMPLETE': {
Store.uploadComplete(data, cb); break;
}
case 'UPLOAD_STATUS': {
Store.uploadStatus(data, cb); break;
}
case 'UPLOAD_CANCEL': {
Store.uploadCancel(data, cb); break;
}
case 'PIN_PADS': {
Store.pinPads(data, cb); break;
}
case 'UNPIN_PADS': {
Store.unpinPads(data, cb); break;
}
case 'GET_PINNED_USAGE': {
Store.getPinnedUsage(data, cb); break;
}
// ANON RPC
case 'INIT_ANON_RPC': {
Store.initAnonRpc(data, cb); break;
}
case 'ANON_RPC_MESSAGE': {
Store.anonRpcMsg(data, cb); break;
}
case 'GET_FILE_SIZE': {
Store.getFileSize(data, cb); break;
}
case 'GET_MULTIPLE_FILE_SIZE': {
Store.getMultipleFileSize(data, cb); break;
}
// Store
case 'GET': {
Store.get(data, cb); break;
}
case 'SET': {
Store.set(data, cb); break;
}
case 'ADD_PAD': {
Store.addPad(data, cb); break;
}
case 'SET_PAD_TITLE': {
Store.setPadTitle(data, cb); break;
}
case 'MOVE_TO_TRASH': {
Store.moveToTrash(data, cb); break;
}
case 'RESET_DRIVE': {
Store.resetDrive(data, cb); break;
}
case 'GET_METADATA': {
Store.getMetadata(data, cb); break;
}
case 'SET_DISPLAY_NAME': {
Store.setDisplayName(data, cb); break;
}
case 'SET_PAD_ATTRIBUTE': {
Store.setPadAttribute(data, cb); break;
}
case 'GET_PAD_ATTRIBUTE': {
Store.getPadAttribute(data, cb); break;
}
case 'SET_ATTRIBUTE': {
Store.setAttribute(data, cb); break;
}
case 'GET_ATTRIBUTE': {
Store.getAttribute(data, cb); break;
}
case 'LIST_ALL_TAGS': {
Store.listAllTags(data, cb); break;
}
case 'GET_TEMPLATES': {
Store.getTemplates(data, cb); break;
}
case 'GET_SECURE_FILES_LIST': {
Store.getSecureFilesList(data, cb); break;
}
case 'GET_STRONGER_HASH': {
Store.getStrongerHash(data, cb); break;
}
// Messaging
case 'INVITE_FROM_USERLIST': {
Store.inviteFromUserlist(data, cb); break;
}
// Messenger
case 'CONTACTS_GET_FRIEND_LIST': {
Store.messenger.getFriendList(data, cb); break;
}
case 'CONTACTS_GET_MY_INFO': {
Store.messenger.getMyInfo(data, cb); break;
}
case 'CONTACTS_GET_FRIEND_INFO': {
Store.messenger.getFriendInfo(data, cb); break;
}
case 'CONTACTS_REMOVE_FRIEND': {
Store.messenger.removeFriend(data, cb); break;
}
case 'CONTACTS_OPEN_FRIEND_CHANNEL': {
Store.messenger.openFriendChannel(data, cb); break;
}
case 'CONTACTS_GET_FRIEND_STATUS': {
Store.messenger.getFriendStatus(data, cb); break;
}
case 'CONTACTS_GET_MORE_HISTORY': {
Store.messenger.getMoreHistory(data, cb); break;
}
case 'CONTACTS_SEND_MESSAGE': {
Store.messenger.sendMessage(data, cb); break;
}
case 'CONTACTS_SET_CHANNEL_HEAD': {
Store.messenger.setChannelHead(data, cb); break;
}
default: {
break;
}
}
};
return Rpc;
});

View file

@ -20,7 +20,7 @@ define([
var sendChunk = function (box, cb) { var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box); var enc = Nacl.util.encodeBase64(box);
common.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) { common.uploadChunk(enc, function (e, msg) {
cb(e, msg); cb(e, msg);
}); });
}; };
@ -58,8 +58,7 @@ define([
if (noStore) { return void onComplete(href); } if (noStore) { return void onComplete(href); }
common.initialPath = path; common.setPadTitle(title || "", href, path, function (err) {
common.renamePad(title || "", href, function (err) {
if (err) { return void console.error(err); } if (err) { return void console.error(err); }
onComplete(href); onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href); common.setPadAttribute('fileType', metadata.type, null, href);

View file

@ -3,13 +3,13 @@ define([
], function (Rpc) { ], function (Rpc) {
var create = function (network, proxy, cb) { var create = function (network, proxy, cb) {
if (!network) { if (!network) {
window.setTimeout(function () { setTimeout(function () {
cb('INVALID_NETWORK'); cb('INVALID_NETWORK');
}); });
return; return;
} }
if (!proxy) { if (!proxy) {
window.setTimeout(function () { setTimeout(function () {
cb('INVALID_PROXY'); cb('INVALID_PROXY');
}); });
return; return;
@ -19,7 +19,7 @@ define([
var edPublic = proxy.edPublic; var edPublic = proxy.edPublic;
if (!(edPrivate && edPublic)) { if (!(edPrivate && edPublic)) {
window.setTimeout(function () { setTimeout(function () {
cb('INVALID_KEYS'); cb('INVALID_KEYS');
}); });
return; return;
@ -39,7 +39,7 @@ define([
// you can ask the server to pin a particular channel for you // you can ask the server to pin a particular channel for you
exp.pin = function (channels, cb) { exp.pin = function (channels, cb) {
if (!Array.isArray(channels)) { if (!Array.isArray(channels)) {
window.setTimeout(function () { setTimeout(function () {
cb('[TypeError] pin expects an array'); cb('[TypeError] pin expects an array');
}); });
return; return;
@ -50,7 +50,7 @@ define([
// you can also ask to unpin a particular channel // you can also ask to unpin a particular channel
exp.unpin = function (channels, cb) { exp.unpin = function (channels, cb) {
if (!Array.isArray(channels)) { if (!Array.isArray(channels)) {
window.setTimeout(function () { setTimeout(function () {
cb('[TypeError] pin expects an array'); cb('[TypeError] pin expects an array');
}); });
return; return;
@ -71,7 +71,7 @@ define([
// if local and remote hashes don't match, send a reset // if local and remote hashes don't match, send a reset
exp.reset = function (channels, cb) { exp.reset = function (channels, cb) {
if (!Array.isArray(channels)) { if (!Array.isArray(channels)) {
window.setTimeout(function () { setTimeout(function () {
cb('[TypeError] pin expects an array'); cb('[TypeError] pin expects an array');
}); });
return; return;
@ -163,7 +163,7 @@ define([
exp.uploadStatus = function (size, cb) { exp.uploadStatus = function (size, cb) {
if (typeof(size) !== 'number') { if (typeof(size) !== 'number') {
return void window.setTimeout(function () { return void setTimeout(function () {
cb('INVALID_SIZE'); cb('INVALID_SIZE');
}); });
} }

View file

@ -140,7 +140,7 @@ types of messages:
var send = ctx.send = function (type, msg, cb) { var send = ctx.send = function (type, msg, cb) {
if (!ctx.connected && type !== 'COOKIE') { if (!ctx.connected && type !== 'COOKIE') {
return void window.setTimeout(function () { return void setTimeout(function () {
cb('DISCONNECTED'); cb('DISCONNECTED');
}); });
} }
@ -185,7 +185,7 @@ types of messages:
send.unauthenticated = function (type, msg, cb) { send.unauthenticated = function (type, msg, cb) {
if (!ctx.connected) { if (!ctx.connected) {
return void window.setTimeout(function () { return void setTimeout(function () {
cb('DISCONNECTED'); cb('DISCONNECTED');
}); });
} }
@ -276,7 +276,7 @@ types of messages:
var send = ctx.send = function (type, msg, cb) { var send = ctx.send = function (type, msg, cb) {
if (!ctx.connected) { if (!ctx.connected) {
return void window.setTimeout(function () { return void setTimeout(function () {
cb('DISCONNECTED'); cb('DISCONNECTED');
}); });
} }

View file

@ -2,14 +2,14 @@
define([ define([
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/api/config', '/api/config',
'jquery', '/common/dom-ready.js',
'/common/requireconfig.js', '/common/requireconfig.js',
'/common/sframe-common-outer.js' '/common/sframe-common-outer.js'
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { ], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
nThen(function (waitFor) { nThen(function (waitFor) {
$(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var req = { var req = {
cfg: requireConfig, cfg: requireConfig,
@ -18,7 +18,7 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
$('#sbox-iframe').attr('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' + ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' +
requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req)));

View file

@ -212,7 +212,9 @@ define([
queue.next(); queue.next();
}; };
var showNamePrompt = true; // Don't show the rename prompt if we don't want to store the file in the drive (avatar)
var showNamePrompt = !config.noStore;
var promptName = function (file, cb) { var promptName = function (file, cb) {
var extIdx = file.name.lastIndexOf('.'); var extIdx = file.name.lastIndexOf('.');
var name = extIdx !== -1 ? file.name.slice(0,extIdx) : file.name; var name = extIdx !== -1 ? file.name.slice(0,extIdx) : file.name;

View file

@ -9,6 +9,7 @@ define([
common.start = function (cfg) { common.start = function (cfg) {
cfg = cfg || {}; cfg = cfg || {};
var realtime = !cfg.noRealtime; var realtime = !cfg.noRealtime;
var network;
var secret; var secret;
var hashes; var hashes;
var CpNfOuter; var CpNfOuter;
@ -18,7 +19,7 @@ define([
var SFrameChannel; var SFrameChannel;
var sframeChan; var sframeChan;
var FilePicker; var FilePicker;
var Messenger; //var Messenger;
var Messaging; var Messaging;
var Notifier; var Notifier;
var Utils = {}; var Utils = {};
@ -32,7 +33,6 @@ define([
'/common/cryptget.js', '/common/cryptget.js',
'/common/sframe-channel.js', '/common/sframe-channel.js',
'/filepicker/main.js', '/filepicker/main.js',
'/common/common-messenger.js',
'/common/common-messaging.js', '/common/common-messaging.js',
'/common/common-notifier.js', '/common/common-notifier.js',
'/common/common-hash.js', '/common/common-hash.js',
@ -41,16 +41,17 @@ define([
'/common/common-constants.js', '/common/common-constants.js',
'/common/common-feedback.js', '/common/common-feedback.js',
'/common/outer/local-store.js', '/common/outer/local-store.js',
'/common/outer/network-config.js',
'/bower_components/netflux-websocket/netflux-client.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel, ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
_FilePicker, _Messenger, _Messaging, _Notifier, _Hash, _Util, _Realtime, _FilePicker, _Messaging, _Notifier, _Hash, _Util, _Realtime,
_Constants, _Feedback, _LocalStore) { _Constants, _Feedback, _LocalStore, NetConfig, Netflux) {
CpNfOuter = _CpNfOuter; CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad; Cryptpad = _Cryptpad;
Crypto = _Crypto; Crypto = _Crypto;
Cryptget = _Cryptget; Cryptget = _Cryptget;
SFrameChannel = _SFrameChannel; SFrameChannel = _SFrameChannel;
FilePicker = _FilePicker; FilePicker = _FilePicker;
Messenger = _Messenger;
Messaging = _Messaging; Messaging = _Messaging;
Notifier = _Notifier; Notifier = _Notifier;
Utils.Hash = _Hash; Utils.Hash = _Hash;
@ -84,7 +85,15 @@ define([
SFrameChannel.create($('#sbox-iframe')[0].contentWindow, waitFor(function (sfc) { SFrameChannel.create($('#sbox-iframe')[0].contentWindow, waitFor(function (sfc) {
sframeChan = sfc; sframeChan = sfc;
}), false, { cache: cache, localStore: localStore, language: Cryptpad.getLanguage() }); }), false, { cache: cache, localStore: localStore, language: Cryptpad.getLanguage() });
Cryptpad.ready(waitFor()); Cryptpad.ready(waitFor(), {
messenger: cfg.messaging
});
if (!cfg.newNetwork) {
Netflux.connect(NetConfig.getWebsocketURL()).then(waitFor(function (nw) {
network = nw;
}));
}
})); }));
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
$('#sbox-iframe').focus(); $('#sbox-iframe').focus();
@ -104,12 +113,23 @@ define([
}); });
}); });
secret = cfg.getSecrets ? cfg.getSecrets(Cryptpad, Utils) : Utils.Hash.getSecrets(); if (cfg.getSecrets) {
if (!secret.channel) { var w = waitFor();
// New pad: create a new random channel id cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) {
secret.channel = Utils.Hash.createChannelId(); secret = s;
Cryptpad.getShareHashes(secret, function (err, h) {
hashes = h;
w();
});
}));
} else {
secret = Utils.Hash.getSecrets();
if (!secret.channel) {
// New pad: create a new random channel id
secret.channel = Utils.Hash.createChannelId();
}
Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; }));
} }
Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; }));
}).nThen(function () { }).nThen(function () {
var readOnly = secret.keys && !secret.keys.editKeyStr; var readOnly = secret.keys && !secret.keys.editKeyStr;
@ -117,59 +137,50 @@ define([
var parsed = Utils.Hash.parsePadUrl(window.location.href); var parsed = Utils.Hash.parsePadUrl(window.location.href);
if (!parsed.type) { throw new Error(); } if (!parsed.type) { throw new Error(); }
var defaultTitle = Utils.Hash.getDefaultName(parsed); var defaultTitle = Utils.Hash.getDefaultName(parsed);
var proxy = Cryptpad.getProxy();
var updateMeta = function () { var updateMeta = function () {
//console.log('EV_METADATA_UPDATE'); //console.log('EV_METADATA_UPDATE');
var name; var metaObj, isTemplate;
nThen(function (waitFor) { nThen(function (waitFor) {
Cryptpad.getLastName(waitFor(function (err, n) { Cryptpad.getMetadata(waitFor(function (err, m) {
if (err) { console.log(err); } if (err) { console.log(err); }
name = n; metaObj = m;
}));
Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) {
if (err) { console.log(err); }
isTemplate = t;
})); }));
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
var metaObj = { metaObj.doc = {
doc: { defaultTitle: defaultTitle,
defaultTitle: defaultTitle, type: parsed.type
type: parsed.type };
}, var additionalPriv = {
user: { accountName: Utils.LocalStore.getAccountName(),
name: name, origin: window.location.origin,
uid: Cryptpad.getUid(), pathname: window.location.pathname,
avatar: Cryptpad.getAvatarUrl(), fileHost: ApiConfig.fileHost,
profile: Cryptpad.getProfileUrl(), readOnly: readOnly,
curvePublic: proxy.curvePublic, availableHashes: hashes,
netfluxId: Cryptpad.getNetwork().webChannels[0].myID, isTemplate: isTemplate,
}, feedbackAllowed: Utils.Feedback.state,
priv: { isPresent: parsed.hashData && parsed.hashData.present,
edPublic: proxy.edPublic, isEmbed: parsed.hashData && parsed.hashData.embed,
accountName: Utils.LocalStore.getAccountName(), accounts: {
origin: window.location.origin, donateURL: Cryptpad.donateURL,
pathname: window.location.pathname, upgradeURL: Cryptpad.upgradeURL
fileHost: ApiConfig.fileHost,
readOnly: readOnly,
availableHashes: hashes,
isTemplate: Cryptpad.isTemplate(window.location.href),
feedbackAllowed: Utils.Feedback.state,
friends: proxy.friends || {},
settings: proxy.settings || {},
isPresent: parsed.hashData && parsed.hashData.present,
isEmbed: parsed.hashData && parsed.hashData.embed,
thumbnails: !((proxy.settings || {}).general || {}).disableThumbnails,
accounts: {
donateURL: Cryptpad.donateURL,
upgradeURL: Cryptpad.upgradeURL
}
} }
}; };
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
if (cfg.addData) { if (cfg.addData) {
cfg.addData(metaObj.priv, Cryptpad); cfg.addData(metaObj.priv, Cryptpad);
} }
sframeChan.event('EV_METADATA_UPDATE', metaObj); sframeChan.event('EV_METADATA_UPDATE', metaObj);
}); });
}; };
Cryptpad.onDisplayNameChanged(updateMeta); Cryptpad.onMetadataChanged(updateMeta);
sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); sframeChan.onReg('EV_METADATA_UPDATE', updateMeta);
proxy.on('change', 'settings', updateMeta);
Utils.LocalStore.onLogout(function () { Utils.LocalStore.onLogout(function () {
sframeChan.event('EV_LOGOUT'); sframeChan.event('EV_LOGOUT');
@ -223,7 +234,7 @@ define([
sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) { sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) {
currentTitle = newTitle; currentTitle = newTitle;
setDocumentTitle(); setDocumentTitle();
Cryptpad.renamePad(newTitle, undefined, function (err) { Cryptpad.setPadTitle(newTitle, undefined, undefined, function (err) {
cb(err); cb(err);
}); });
}); });
@ -241,7 +252,7 @@ define([
cb('ERROR'); cb('ERROR');
return; return;
} }
Cryptpad.changeDisplayName(newName, true); Cryptpad.changeMetadata();
cb(); cb();
}); });
}); });
@ -287,7 +298,6 @@ define([
}; };
sframeChan.on('Q_GET_FULL_HISTORY', function (data, cb) { sframeChan.on('Q_GET_FULL_HISTORY', function (data, cb) {
var network = Cryptpad.getNetwork();
var hkn = network.historyKeeper; var hkn = network.historyKeeper;
var crypto = Crypto.createEncryptor(secret.keys); var crypto = Crypto.createEncryptor(secret.keys);
// Get the history messages and send them to the iframe // Get the history messages and send them to the iframe
@ -445,8 +455,9 @@ define([
Cryptpad.useTemplate(href, Cryptget, cb); Cryptpad.useTemplate(href, Cryptget, cb);
}); });
sframeChan.on('Q_TEMPLATE_EXIST', function (type, cb) { sframeChan.on('Q_TEMPLATE_EXIST', function (type, cb) {
var hasTemplate = Cryptpad.listTemplates(type).length > 0; Cryptpad.listTemplates(type, function (err, templates) {
cb(hasTemplate); cb(templates.length > 0);
});
}); });
sframeChan.on('EV_GOTO_URL', function (url) { sframeChan.on('EV_GOTO_URL', function (url) {
@ -494,117 +505,58 @@ define([
} }
if (cfg.messaging) { if (cfg.messaging) {
var messenger = Messenger.messenger(Cryptpad);
sframeChan.on('Q_CONTACTS_GET_FRIEND_LIST', function (data, cb) { sframeChan.on('Q_CONTACTS_GET_FRIEND_LIST', function (data, cb) {
messenger.getFriendList(function (e, keys) { Cryptpad.messenger.getFriendList(cb);
cb({
error: e,
data: keys,
});
});
}); });
sframeChan.on('Q_CONTACTS_GET_MY_INFO', function (data, cb) { sframeChan.on('Q_CONTACTS_GET_MY_INFO', function (data, cb) {
messenger.getMyInfo(function (e, info) { Cryptpad.messenger.getMyInfo(cb);
cb({
error: e,
data: info,
});
});
}); });
sframeChan.on('Q_CONTACTS_GET_FRIEND_INFO', function (curvePublic, cb) { sframeChan.on('Q_CONTACTS_GET_FRIEND_INFO', function (curvePublic, cb) {
messenger.getFriendInfo(curvePublic, function (e, info) { Cryptpad.messenger.getFriendInfo(curvePublic, cb);
cb({
error: e,
data: info,
});
});
}); });
sframeChan.on('Q_CONTACTS_REMOVE_FRIEND', function (curvePublic, cb) { sframeChan.on('Q_CONTACTS_REMOVE_FRIEND', function (curvePublic, cb) {
messenger.removeFriend(curvePublic, function (e, info) { Cryptpad.messenger.removeFriend(curvePublic, cb);
cb({
error: e,
data: info,
});
});
}); });
sframeChan.on('Q_CONTACTS_OPEN_FRIEND_CHANNEL', function (curvePublic, cb) { sframeChan.on('Q_CONTACTS_OPEN_FRIEND_CHANNEL', function (curvePublic, cb) {
messenger.openFriendChannel(curvePublic, function (e) { Cryptpad.messenger.openFriendChannel(curvePublic, cb);
cb({ error: e, });
});
}); });
sframeChan.on('Q_CONTACTS_GET_STATUS', function (curvePublic, cb) { sframeChan.on('Q_CONTACTS_GET_STATUS', function (curvePublic, cb) {
messenger.getStatus(curvePublic, function (e, online) { Cryptpad.messenger.getFriendStatus(curvePublic, cb);
cb({
error: e,
data: online,
});
});
}); });
sframeChan.on('Q_CONTACTS_GET_MORE_HISTORY', function (opt, cb) { sframeChan.on('Q_CONTACTS_GET_MORE_HISTORY', function (opt, cb) {
messenger.getMoreHistory(opt.curvePublic, opt.sig, opt.count, function (e, history) { Cryptpad.messenger.getMoreHistory(opt, cb);
cb({
error: e,
data: history,
});
});
}); });
sframeChan.on('Q_CONTACTS_SEND_MESSAGE', function (opt, cb) { sframeChan.on('Q_CONTACTS_SEND_MESSAGE', function (opt, cb) {
messenger.sendMessage(opt.curvePublic, opt.content, function (e) { Cryptpad.messenger.sendMessage(opt, cb);
cb({
error: e,
});
});
}); });
sframeChan.on('Q_CONTACTS_SET_CHANNEL_HEAD', function (opt, cb) { sframeChan.on('Q_CONTACTS_SET_CHANNEL_HEAD', function (opt, cb) {
messenger.setChannelHead(opt.curvePublic, opt.sig, function (e) { Cryptpad.messenger.setChannelHead(opt, cb);
cb({
error: e
});
});
}); });
sframeChan.on('Q_CONTACTS_CLEAR_OWNED_CHANNEL', function (channel, cb) { sframeChan.on('Q_CONTACTS_CLEAR_OWNED_CHANNEL', function (channel, cb) {
messenger.clearOwnedChannel(channel, function (e) { Cryptpad.clearOwnedChannel(channel, cb);
cb({
error: e,
});
});
}); });
messenger.on('message', function (message) { Cryptpad.messenger.onMessageEvent.reg(function (data) {
sframeChan.event('EV_CONTACTS_MESSAGE', message); sframeChan.event('EV_CONTACTS_MESSAGE', data);
}); });
messenger.on('join', function (curvePublic, channel) { Cryptpad.messenger.onJoinEvent.reg(function (data) {
sframeChan.event('EV_CONTACTS_JOIN', { sframeChan.event('EV_CONTACTS_JOIN', data);
curvePublic: curvePublic,
channel: channel,
});
}); });
messenger.on('leave', function (curvePublic, channel) { Cryptpad.messenger.onLeaveEvent.reg(function (data) {
sframeChan.event('EV_CONTACTS_LEAVE', { sframeChan.event('EV_CONTACTS_LEAVE', data);
curvePublic: curvePublic,
channel: channel,
});
}); });
messenger.on('update', function (info, curvePublic) { Cryptpad.messenger.onUpdateEvent.reg(function (data) {
sframeChan.event('EV_CONTACTS_UPDATE', { sframeChan.event('EV_CONTACTS_UPDATE', data);
curvePublic: curvePublic,
info: info,
});
}); });
messenger.on('friend', function (curvePublic) { Cryptpad.messenger.onFriendEvent.reg(function (data) {
sframeChan.event('EV_CONTACTS_FRIEND', { sframeChan.event('EV_CONTACTS_FRIEND', data);
curvePublic: curvePublic,
});
}); });
messenger.on('unfriend', function (curvePublic) { Cryptpad.messenger.onUnfriendEvent.reg(function (data) {
sframeChan.event('EV_CONTACTS_UNFRIEND', { sframeChan.event('EV_CONTACTS_UNFRIEND', data);
curvePublic: curvePublic,
});
}); });
} }
@ -629,7 +581,7 @@ define([
CpNfOuter.start({ CpNfOuter.start({
sframeChan: sframeChan, sframeChan: sframeChan,
channel: secret.channel, channel: secret.channel,
network: cfg.newNetwork || Cryptpad.getNetwork(), network: cfg.newNetwork || network,
validateKey: secret.keys.validateKey || undefined, validateKey: secret.keys.validateKey || undefined,
readOnly: readOnly, readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys), crypto: Crypto.createEncryptor(secret.keys),

View file

@ -328,7 +328,7 @@ define([
nThen(function (waitFor) { nThen(function (waitFor) {
SFrameChannel.create(window.parent, waitFor(function (sfc) { ctx.sframeChan = sfc; }), true); SFrameChannel.create(window.parent, waitFor(function (sfc) { ctx.sframeChan = sfc; }), true);
// CpNfInner.start() should be here.... // CpNfInner.start() should be here....
}).nThen(function () { }).nThen(function (waitFor) {
localForage.clear(); localForage.clear();
ctx.metadataMgr = MetadataMgr.create(ctx.sframeChan); ctx.metadataMgr = MetadataMgr.create(ctx.sframeChan);
@ -373,10 +373,6 @@ define([
}); });
}); });
ctx.sframeChan.on('EV_RT_CONNECT', function () { CommonRealtime.setConnectionState(true); });
ctx.sframeChan.on('EV_RT_DISCONNECT', function () { CommonRealtime.setConnectionState(false); });
ctx.sframeChan.on('Q_INCOMING_FRIEND_REQUEST', function (confirmMsg, cb) { ctx.sframeChan.on('Q_INCOMING_FRIEND_REQUEST', function (confirmMsg, cb) {
UI.confirm(confirmMsg, cb, null, true); UI.confirm(confirmMsg, cb, null, true);
}); });
@ -393,6 +389,8 @@ define([
} catch (e) { Feedback.init(false); } } catch (e) { Feedback.init(false); }
}); });
ctx.metadataMgr.onReady(waitFor());
}).nThen(function () {
ctx.sframeChan.ready(); ctx.sframeChan.ready();
cb(funcs); cb(funcs);
}); });

View file

@ -767,7 +767,7 @@ define([
var origin = privateData.origin; var origin = privateData.origin;
var pathname = privateData.pathname; var pathname = privateData.pathname;
var href = inDrive.test(pathname) ? origin+'/index.html' : origin+'/drive/'; var href = inDrive.test(pathname) ? origin+'/index.html' : origin+'/drive/';
var buttonTitle = inDrive ? Messages.header_homeTitle : Messages.header_logoTitle; var buttonTitle = inDrive.test(pathname) ? Messages.header_homeTitle : Messages.header_logoTitle;
var $aTag = $('<a>', { var $aTag = $('<a>', {
href: href, href: href,

View file

@ -1,12 +1,11 @@
define([ define([
'jquery',
'/customize/application_config.js', '/customize/application_config.js',
'/common/common-util.js', '/common/common-util.js',
'/common/common-hash.js', '/common/common-hash.js',
'/common/common-realtime.js', '/common/common-realtime.js',
'/common/common-constants.js', '/common/common-constants.js',
'/customize/messages.js' '/customize/messages.js'
], function ($, AppConfig, Util, Hash, Realtime, Constants, Messages) { ], function (AppConfig, Util, Hash, Realtime, Constants, Messages) {
var module = {}; var module = {};
var ROOT = module.ROOT = "root"; var ROOT = module.ROOT = "root";
@ -21,13 +20,13 @@ define([
module.init = function (files, config) { module.init = function (files, config) {
var exp = {}; var exp = {};
var Cryptpad = config.Cryptpad; var pinPads = config.pinPads;
var loggedIn = config.loggedIn; var loggedIn = config.loggedIn;
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey; var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey;
var OLD_FILES_DATA = module.OLD_FILES_DATA = exp.OLD_FILES_DATA = Constants.oldStorageKey; var OLD_FILES_DATA = module.OLD_FILES_DATA = exp.OLD_FILES_DATA = Constants.oldStorageKey;
var NEW_FOLDER_NAME = Messages.fm_newFolder; var NEW_FOLDER_NAME = Messages.fm_newFolder || 'New folder';
var NEW_FILE_NAME = Messages.fm_newFile; var NEW_FILE_NAME = Messages.fm_newFile || 'New file';
exp.ROOT = ROOT; exp.ROOT = ROOT;
exp.UNSORTED = UNSORTED; exp.UNSORTED = UNSORTED;
@ -101,7 +100,7 @@ define([
}; };
for (var f in element) { for (var f in element) {
if (trashRoot) { if (trashRoot) {
if ($.isArray(element[f])) { if (Array.isArray(element[f])) {
element[f].forEach(addSubfolder); element[f].forEach(addSubfolder);
} }
} else { } else {
@ -119,7 +118,7 @@ define([
}; };
for (var f in element) { for (var f in element) {
if (trashRoot) { if (trashRoot) {
if ($.isArray(element[f])) { if (Array.isArray(element[f])) {
element[f].forEach(addFile); element[f].forEach(addFile);
} }
} else { } else {
@ -148,14 +147,14 @@ define([
return data.filename || data.title || NEW_FILE_NAME; return data.filename || data.title || NEW_FILE_NAME;
}; };
exp.getPadAttribute = function (href, attr, cb) { exp.getPadAttribute = function (href, attr, cb) {
cb = cb || $.noop; cb = cb || function () {};
var id = exp.getIdFromHref(href); var id = exp.getIdFromHref(href);
if (!id) { return void cb(null, undefined); } if (!id) { return void cb(null, undefined); }
var data = getFileData(id); var data = getFileData(id);
cb(null, clone(data[attr])); cb(null, clone(data[attr]));
}; };
exp.setPadAttribute = function (href, attr, value, cb) { exp.setPadAttribute = function (href, attr, value, cb) {
cb = cb || $.noop; cb = cb || function () {};
var id = exp.getIdFromHref(href); var id = exp.getIdFromHref(href);
if (!id) { return void cb("E_INVAL_HREF"); } if (!id) { return void cb("E_INVAL_HREF"); }
if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); } if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
@ -167,7 +166,7 @@ define([
// PATHS // PATHS
var comparePath = exp.comparePath = function (a, b) { var comparePath = exp.comparePath = function (a, b) {
if (!a || !b || !$.isArray(a) || !$.isArray(b)) { return false; } if (!a || !b || !Array.isArray(a) || !Array.isArray(b)) { return false; }
if (a.length !== b.length) { return false; } if (a.length !== b.length) { return false; }
var result = true; var result = true;
var i = a.length - 1; var i = a.length - 1;
@ -265,7 +264,7 @@ define([
} }
}; };
for (var e in root) { for (var e in root) {
if (!$.isArray(root[e])) { if (!Array.isArray(root[e])) {
error("Trash contains a non-array element"); error("Trash contains a non-array element");
return; return;
} }
@ -487,8 +486,6 @@ define([
// FILES DATA // FILES DATA
exp.pushData = function (data, cb) { exp.pushData = function (data, cb) {
// TODO: can only be called from outside atm
if (!Cryptpad) { return; }
if (typeof cb !== "function") { cb = function () {}; } if (typeof cb !== "function") { cb = function () {}; }
var todo = function () { var todo = function () {
var id = Util.createRandomInteger(); var id = Util.createRandomInteger();
@ -498,8 +495,9 @@ define([
if (!loggedIn || !AppConfig.enablePinning || config.testMode) { if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void todo(); return void todo();
} }
Cryptpad.pinPads([Hash.hrefToHexChannelId(data.href)], function (e) { if (!pinPads) { return; }
if (e) { return void cb(e); } pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
todo(); todo();
}); });
}; };
@ -968,7 +966,7 @@ define([
var addToClean = function (obj, idx, el) { var addToClean = function (obj, idx, el) {
if (typeof(obj) !== "object") { toClean.push(idx); return; } if (typeof(obj) !== "object") { toClean.push(idx); return; }
if (!isFile(obj.element, true) && !isFolder(obj.element)) { toClean.push(idx); return; } if (!isFile(obj.element, true) && !isFolder(obj.element)) { toClean.push(idx); return; }
if (!$.isArray(obj.path)) { toClean.push(idx); return; } if (!Array.isArray(obj.path)) { toClean.push(idx); return; }
if (typeof obj.element === "string") { if (typeof obj.element === "string") {
// We have an old file (href) which is not in filesData: add it // We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger(); var id = Util.createRandomInteger();

94
www/common/wire.js Normal file
View file

@ -0,0 +1,94 @@
define([
], function () {
var Wire = {};
/* MISSION: write a generic RPC framework
Requirements
* some transmission methods can be interrupted
* handle disconnects and reconnects
* handle callbacks
* configurable timeout
* Service should expose 'addClient' method
* and handle broadcast
*
*/
var uid = function () {
return Number(Math.floor(Math.random () *
Number.MAX_SAFE_INTEGER)).toString(32);
};
/*
opt = {
send: function () {
},
receive: function () {
},
constructor: function (cb) {
cb(void 0 , {
send: function (content, cb) {
},
receive: function () {
}
});
},
};
*/
Wire.create = function (opt, cb) {
var ctx = {};
var pending = ctx.pending = {};
ctx.connected = false;
var rpc = {};
opt.constructor(function (e, service) {
if (e) { return setTimeout(function () { cb(e); }); }
rpc.send = function (type, data, cb) {
var txid = uid();
if (typeof(cb) !== 'function') {
throw new Error('expected callback');
}
ctx.pending[txid] = function (err, response) {
cb(err, response);
};
service.send(JSON.stringify({
txid: txid,
message: {
command: type,
content: data,
},
}));
};
service.receive(function (raw) {
try {
var data = JSON.parse(raw);
var txid = data.txid;
if (!txid) { throw new Error('NO_TXID'); }
var cb = pending[txid];
if (data.error) { return void cb(data.error); }
cb(void 0, data.content);
} catch (e) { console.error("UNHANDLED_MESSAGE", raw); }
});
cb(void 0, rpc);
});
};
return Wire;
});

View file

@ -2,15 +2,15 @@
define([ define([
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/api/config', '/api/config',
'jquery', '/common/dom-ready.js',
'/common/requireconfig.js', '/common/requireconfig.js',
'/common/sframe-common-outer.js' '/common/sframe-common-outer.js'
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { ], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
nThen(function (waitFor) { nThen(function (waitFor) {
$(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var req = { var req = {
cfg: requireConfig, cfg: requireConfig,
@ -19,7 +19,7 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
$('#sbox-iframe').attr('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/contacts/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/contacts/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));

View file

@ -1,10 +1,8 @@
define([ define([
'jquery', 'jquery',
'/bower_components/chainpad-crypto/crypto.js', '/bower_components/chainpad-crypto/crypto.js',
'/bower_components/textpatcher/TextPatcher.js',
'/common/toolbar3.js', '/common/toolbar3.js',
'json.sortify', 'json.sortify',
'/bower_components/chainpad-json-validator/json-ot.js',
'/common/common-util.js', '/common/common-util.js',
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/common/sframe-common.js', '/common/sframe-common.js',
@ -22,10 +20,8 @@ define([
], function ( ], function (
$, $,
Crypto, Crypto,
TextPatcher,
Toolbar, Toolbar,
JSONSortify, JSONSortify,
JsonOT,
Util, Util,
nThen, nThen,
SFCommon, SFCommon,
@ -61,7 +57,6 @@ define([
var config = APP.config = { var config = APP.config = {
readOnly: readOnly, readOnly: readOnly,
transformFunction: JsonOT.validate,
// cryptpad debug logging (default is 1) // cryptpad debug logging (default is 1)
// logLevel: 0, // logLevel: 0,
validateContent: function (content) { validateContent: function (content) {
@ -123,11 +118,7 @@ define([
config.onReady = function (info) { config.onReady = function (info) {
if (APP.realtime !== info.realtime) { if (APP.realtime !== info.realtime) {
var realtime = APP.realtime = info.realtime; APP.realtime = info.realtime;
APP.patchText = TextPatcher.create({
realtime: realtime,
//logging: true
});
} }
var userDoc = APP.realtime.getUserDoc(); var userDoc = APP.realtime.getUserDoc();

View file

@ -2,17 +2,15 @@
define([ define([
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/api/config', '/api/config',
'jquery', '/common/dom-ready.js',
'/common/requireconfig.js', '/common/requireconfig.js',
'/common/sframe-common-outer.js', '/common/sframe-common-outer.js',
'/common/outer/network-config.js', ], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
'/bower_components/netflux-websocket/netflux-client.js',
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO, NetConfig, Netflux) {
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
nThen(function (waitFor) { nThen(function (waitFor) {
$(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var req = { var req = {
cfg: requireConfig, cfg: requireConfig,
@ -21,7 +19,7 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
$('#sbox-iframe').attr('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/drive/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/drive/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));
@ -38,10 +36,10 @@ define([
}; };
window.addEventListener('message', onMsg); window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
var getSecrets = function (Cryptpad, Utils) { var getSecrets = function (Cryptpad, Utils, cb) {
var hash = window.location.hash.slice(1) || Utils.LocalStore.getUserHash() || var hash = window.location.hash.slice(1) || Utils.LocalStore.getUserHash() ||
Utils.LocalStore.getFSHash(); Utils.LocalStore.getFSHash();
return Utils.Hash.getSecrets('drive', hash); cb(null, Utils.Hash.getSecrets('drive', hash));
}; };
var addRpc = function (sframeChan, Cryptpad, Utils) { var addRpc = function (sframeChan, Cryptpad, Utils) {
sframeChan.on('EV_BURN_ANON_DRIVE', function () { sframeChan.on('EV_BURN_ANON_DRIVE', function () {
@ -51,13 +49,13 @@ define([
window.location.reload(); window.location.reload();
}); });
}; };
Netflux.connect(NetConfig.getWebsocketURL()).then(function (network) { //Netflux.connect(NetConfig.getWebsocketURL()).then(function (network) {
SFCommonO.start({ SFCommonO.start({
getSecrets: getSecrets, getSecrets: getSecrets,
newNetwork: network, //newNetwork: network,
noHash: true, noHash: true,
addRpc: addRpc addRpc: addRpc
}); });
}, function (err) { console.error(err); }); //}, function (err) { console.error(err); });
}); });
}); });

View file

@ -70,7 +70,7 @@ define([
module.test = function (assert) { module.test = function (assert) {
var config = { var config = {
Cryptpad: Cryptpad, pinPads: Cryptpad.pinPads,
workgroup: false, workgroup: false,
testMode: true, testMode: true,
loggedIn: false loggedIn: false
@ -325,7 +325,12 @@ define([
var fo = FO.init(files, config); var fo = FO.init(files, config);
fo.fixFiles(); fo.fixFiles();
var data = Cryptpad.makePad(href5, 'Title5'); var data = {
href: href5,
title: 'Title5',
atime: +new Date(),
ctime: +new Date()
};
var res; var res;
var id5; var id5;
// pushData is synchronous in test mode (no pinning) // pushData is synchronous in test mode (no pinning)

View file

@ -120,7 +120,6 @@ define([
decrypted.callback(); decrypted.callback();
} }
console.log(decrypted);
$dlview.show(); $dlview.show();
$dlform.hide(); $dlform.hide();
var $dlButton = $dlview.find('media-tag button'); var $dlButton = $dlview.find('media-tag button');
@ -174,7 +173,6 @@ define([
var progress = e.originalEvent; var progress = e.originalEvent;
var p = progress.percent +'%'; var p = progress.percent +'%';
$progress.width(p); $progress.width(p);
console.log(progress.percent);
}); });
/** /**

View file

@ -2,15 +2,15 @@
define([ define([
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/api/config', '/api/config',
'jquery', '/common/dom-ready.js',
'/common/requireconfig.js', '/common/requireconfig.js',
'/common/sframe-common-outer.js' '/common/sframe-common-outer.js'
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { ], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
nThen(function (waitFor) { nThen(function (waitFor) {
$(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var req = { var req = {
cfg: requireConfig, cfg: requireConfig,
@ -19,7 +19,7 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
$('#sbox-iframe').attr('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/file/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/file/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));

View file

@ -46,41 +46,30 @@ define([
sframeChan = sfc; sframeChan = sfc;
})); }));
}).nThen(function () { }).nThen(function () {
var proxy = Cryptpad.getProxy();
var updateMeta = function () { var updateMeta = function () {
//console.log('EV_METADATA_UPDATE'); //console.log('EV_METADATA_UPDATE');
var name; var metaObj;
nThen(function (waitFor) { nThen(function (waitFor) {
Cryptpad.getLastName(waitFor(function (err, n) { Cryptpad.getMetadata(waitFor(function (err, n) {
if (err) { console.log(err); } if (err) { console.log(err); }
name = n; metaObj = n;
})); }));
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
sframeChan.event('EV_METADATA_UPDATE', { metaObj.doc = {};
doc: {}, var additionalPriv = {
user: { accountName: Utils.LocalStore.getAccountName(),
name: name, origin: window.location.origin,
uid: Cryptpad.getUid(), pathname: window.location.pathname,
avatar: Cryptpad.getAvatarUrl(), feedbackAllowed: Utils.Feedback.state,
profile: Cryptpad.getProfileUrl(), types: config.types
curvePublic: proxy.curvePublic, };
netfluxId: Cryptpad.getNetwork().webChannels[0].myID, for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
},
priv: { sframeChan.event('EV_METADATA_UPDATE', metaObj);
accountName: Utils.LocalStore.getAccountName(),
origin: window.location.origin,
pathname: window.location.pathname,
feedbackAllowed: Utils.Feedback.state,
friends: proxy.friends || {},
settings: proxy.settings || {},
types: config.types
}
});
}); });
}; };
Cryptpad.onDisplayNameChanged(updateMeta); Cryptpad.onMetadataChanged(updateMeta);
sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); sframeChan.onReg('EV_METADATA_UPDATE', updateMeta);
proxy.on('change', 'settings', updateMeta);
config.addCommonRpc(sframeChan); config.addCommonRpc(sframeChan);

View file

@ -2,15 +2,15 @@
define([ define([
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/api/config', '/api/config',
'jquery', '/common/dom-ready.js',
'/common/requireconfig.js', '/common/requireconfig.js',
'/common/sframe-common-outer.js', '/common/sframe-common-outer.js',
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { ], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
nThen(function (waitFor) { nThen(function (waitFor) {
$(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var req = { var req = {
cfg: requireConfig, cfg: requireConfig,
@ -19,7 +19,7 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
$('#sbox-iframe').attr('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/poll/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/poll/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));

View file

@ -73,6 +73,9 @@
margin: 5px; margin: 5px;
} }
} }
.cp-app-profile-resizer {
text-align: center;
}
#cp-app-profile-displayname, #cp-app-profile-link { #cp-app-profile-displayname, #cp-app-profile-link {
width: 100%; width: 100%;
height: 40px; height: 40px;

View file

@ -20,6 +20,8 @@ define([
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less', 'less!/customize/src/less2/main.less',
'/bower_components/croppie/croppie.min.js',
'css!/bower_components/croppie/croppie.css',
], function ( ], function (
$, $,
Crypto, Crypto,
@ -253,12 +255,44 @@ define([
createEditableInput($block, LINK_ID, placeholder, getValue, setValue); createEditableInput($block, LINK_ID, placeholder, getValue, setValue);
}; };
var AVATAR_SIZE_LIMIT = 0.5;
var allowedMediaTypes = [ var allowedMediaTypes = [
'image/png', 'image/png',
'image/jpeg', 'image/jpeg',
'image/jpg', 'image/jpg',
'image/gif', 'image/gif',
]; ];
var transformAvatar = function (file, cb) {
if (file.type === 'image/gif') { return void cb(file); }
var $croppie = $('<div>', {
'class': 'cp-app-profile-resizer'
});
var todo = function () {
UI.confirm($croppie[0], function (yes) {
if (!yes) { return; }
$croppie.croppie('result', {
type: 'blob',
size: {width: 300, height: 300}
}).then(function(blob) {
blob.lastModifiedDate = new Date();
blob.name = 'avatar';
cb(blob);
});
});
};
var reader = new FileReader();
reader.onload = function(e) {
$croppie.croppie({
url: e.target.result,
viewport: { width: 100, height: 100 },
boundary: { width: 400, height: 300 },
});
todo();
};
reader.readAsDataURL(file);
};
var addAvatar = function ($container) { var addAvatar = function ($container) {
var $block = $('<div>', {id: AVATAR_ID}).appendTo($container); var $block = $('<div>', {id: AVATAR_ID}).appendTo($container);
var $span = $('<span>').appendTo($block); var $span = $('<span>').appendTo($block);
@ -318,14 +352,30 @@ define([
} }
}; };
APP.FM = common.createFileManager(fmConfig); APP.FM = common.createFileManager(fmConfig);
var accepted = ".gif,.jpg,.jpeg,.png";
var data = { var data = {
FM: APP.FM, FM: APP.FM,
filter: function (file) { filter: function (file) {
var sizeMB = Util.bytesToMegabytes(file.size); var sizeMB = Util.bytesToMegabytes(file.size);
var type = file.type; var type = file.type;
return sizeMB <= 0.5 && allowedMediaTypes.indexOf(type) !== -1; // We can't resize .gif so we have to display an error if it is too big
if (sizeMB > AVATAR_SIZE_LIMIT && type === 'image/gif') {
UI.log(Messages._getKey('profile_uploadSizeError', [
Messages._getKey('formattedMB', [AVATAR_SIZE_LIMIT])
]));
return false;
}
// Display an error if the image type is not allowed
if (allowedMediaTypes.indexOf(type) === -1) {
UI.log(Messages._getKey('profile_uploadTypeError', [
accepted.split(',').join(', ')
]));
return false;
}
return true;
}, },
accept: ".gif,.jpg,.jpeg,.png" transformer: transformAvatar,
accept: accepted
}; };
var $upButton = common.createButton('upload', false, data); var $upButton = common.createButton('upload', false, data);
$upButton.text(Messages.profile_upload); $upButton.text(Messages.profile_upload);
@ -395,15 +445,6 @@ define([
var onReady = function () { var onReady = function () {
APP.$container.find('#'+CREATE_ID).remove(); APP.$container.find('#'+CREATE_ID).remove();
/*var obj = APP.lm && APP.lm.proxy;
if (!APP.readOnly) {
var pubKeys = Cryptpad.getPublicKeys();
if (pubKeys && pubKeys.curve) {
obj.curveKey = pubKeys.curve;
obj.edKey = pubKeys.ed;
}
}*/
if (!APP.initialized) { if (!APP.initialized) {
var $header = $('<div>', {id: HEADER_ID}).appendTo(APP.$rightside); var $header = $('<div>', {id: HEADER_ID}).appendTo(APP.$rightside);
addAvatar($header); addAvatar($header);

View file

@ -2,15 +2,15 @@
define([ define([
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/api/config', '/api/config',
'jquery', '/common/dom-ready.js',
'/common/requireconfig.js', '/common/requireconfig.js',
'/common/sframe-common-outer.js', '/common/sframe-common-outer.js',
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { ], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
nThen(function (waitFor) { nThen(function (waitFor) {
$(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var req = { var req = {
cfg: requireConfig, cfg: requireConfig,
@ -19,7 +19,7 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
$('#sbox-iframe').attr('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/profile/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/profile/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));
@ -36,34 +36,41 @@ define([
}; };
window.addEventListener('message', onMsg); window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
var getSecrets = function (Cryptpad, Utils) { var getSecrets = function (Cryptpad, Utils, cb) {
var Hash = Utils.Hash; var Hash = Utils.Hash;
// 1st case: visiting someone else's profile with hash in the URL // 1st case: visiting someone else's profile with hash in the URL
if (window.location.hash) { if (window.location.hash) {
return Hash.getSecrets('profile', window.location.hash.slice(1)); return void cb(null, Hash.getSecrets('profile', window.location.hash.slice(1)));
} }
// 2nd case: visiting our own existing profile var editHash;
var obj = Cryptpad.getProxy(); nThen(function (waitFor) {
if (obj.profile && obj.profile.view && obj.profile.edit) { // 2nd case: visiting our own existing profile
return Hash.getSecrets('profile', obj.profile.edit); Cryptpad.getProfileEditUrl(waitFor(function (hash) {
} editHash = hash;
// 3rd case: profile creation (create a new random hash, store it later if needed) }));
if (!Utils.LocalStore.isLoggedIn()) { return; } }).nThen(function () {
var hash = Hash.createRandomHash(); if (editHash) {
var secret = Hash.getSecrets('profile', hash); return void cb(null, Hash.getSecrets('profile', editHash));
Cryptpad.pinPads([secret.channel], function (e) {
if (e) {
if (e === 'E_OVER_LIMIT') {
// TODO
}
return;
//return void UI.log(Messages._getKey('profile_error', [e])) // TODO
} }
obj.profile = {}; // 3rd case: profile creation (create a new random hash, store it later if needed)
obj.profile.edit = Utils.Hash.getEditHashFromKeys(secret.channel, secret.keys); if (!Utils.LocalStore.isLoggedIn()) { return void cb(); }
obj.profile.view = Utils.Hash.getViewHashFromKeys(secret.channel, secret.keys); var hash = Hash.createRandomHash();
var secret = Hash.getSecrets('profile', hash);
Cryptpad.pinPads([secret.channel], function (e) {
if (e) {
if (e === 'E_OVER_LIMIT') {
// TODO
}
return;
//return void UI.log(Messages._getKey('profile_error', [e])) // TODO
}
var profile = {};
profile.edit = Utils.Hash.getEditHashFromKeys(secret.channel, secret.keys);
profile.view = Utils.Hash.getViewHashFromKeys(secret.channel, secret.keys);
Cryptpad.setNewProfile(profile);
});
cb(null, secret);
}); });
return secret;
}; };
var addRpc = function (sframeChan, Cryptpad, Utils) { var addRpc = function (sframeChan, Cryptpad, Utils) {
// Adding a new avatar from the profile: pin it and store it in the object // Adding a new avatar from the profile: pin it and store it in the object
@ -71,18 +78,14 @@ define([
var chanId = Utils.Hash.hrefToHexChannelId(data); var chanId = Utils.Hash.hrefToHexChannelId(data);
Cryptpad.pinPads([chanId], function (e) { Cryptpad.pinPads([chanId], function (e) {
if (e) { return void cb(e); } if (e) { return void cb(e); }
Cryptpad.getProxy().profile.avatar = data; Cryptpad.setAvatar(data, cb);
Utils.Realtime.whenRealtimeSyncs(Cryptpad.getRealtime(), function () {
cb();
});
}); });
}); });
// Removing the avatar from the profile: unpin it // Removing the avatar from the profile: unpin it
sframeChan.on('Q_PROFILE_AVATAR_REMOVE', function (data, cb) { sframeChan.on('Q_PROFILE_AVATAR_REMOVE', function (data, cb) {
var chanId = Utils.Hash.hrefToHexChannelId(data); var chanId = Utils.Hash.hrefToHexChannelId(data);
Cryptpad.unpinPads([chanId], function (e) { Cryptpad.unpinPads([chanId], function () {
delete Cryptpad.getProxy().profile.avatar; Cryptpad.setAvatar(undefined, cb);
cb(e);
}); });
}); });
}; };

View file

@ -2,15 +2,15 @@
define([ define([
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/api/config', '/api/config',
'jquery', '/common/dom-ready.js',
'/common/requireconfig.js', '/common/requireconfig.js',
'/common/sframe-common-outer.js' '/common/sframe-common-outer.js'
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { ], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
nThen(function (waitFor) { nThen(function (waitFor) {
$(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var req = { var req = {
cfg: requireConfig, cfg: requireConfig,
@ -19,7 +19,7 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
$('#sbox-iframe').attr('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/settings/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/settings/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));
@ -43,7 +43,7 @@ define([
}); });
}); });
sframeChan.on('Q_SETTINGS_DRIVE_GET', function (d, cb) { sframeChan.on('Q_SETTINGS_DRIVE_GET', function (d, cb) {
cb(Cryptpad.getProxy()); Cryptpad.getUserObject(cb);
}); });
sframeChan.on('Q_SETTINGS_DRIVE_SET', function (data, cb) { sframeChan.on('Q_SETTINGS_DRIVE_SET', function (data, cb) {
var sjson = JSON.stringify(data); var sjson = JSON.stringify(data);
@ -57,26 +57,13 @@ define([
}); });
}); });
sframeChan.on('Q_SETTINGS_DRIVE_RESET', function (data, cb) { sframeChan.on('Q_SETTINGS_DRIVE_RESET', function (data, cb) {
var proxy = Cryptpad.getProxy(); Cryptpad.resetDrive(cb);
var realtime = Cryptpad.getRealtime();
proxy.drive = Cryptpad.getStore().getEmptyObject();
Utils.Realtime.whenRealtimeSyncs(realtime, cb);
}); });
sframeChan.on('Q_SETTINGS_LOGOUT', function (data, cb) { sframeChan.on('Q_SETTINGS_LOGOUT', function (data, cb) {
var proxy = Cryptpad.getProxy(); Cryptpad.logoutFromAll(cb);
var realtime = Cryptpad.getRealtime();
var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
localStorage.setItem('loginToken', token);
proxy.loginToken = token;
Utils.Realtime.whenRealtimeSyncs(realtime, cb);
}); });
sframeChan.on('Q_SETTINGS_IMPORT_LOCAL', function (data, cb) { sframeChan.on('Q_SETTINGS_IMPORT_LOCAL', function (data, cb) {
var proxyData = Cryptpad.getStore().getProxy(); Cryptpad.mergeAnonDrive(cb);
require([
'/common/mergeDrive.js',
], function (Merge) {
Merge.anonDriveIntoUser(proxyData, Utils.LocalStore.getFSHash(), cb);
});
}); });
}; };
SFCommonO.start({ SFCommonO.start({

View file

@ -2,15 +2,15 @@
define([ define([
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/api/config', '/api/config',
'jquery', '/common/dom-ready.js',
'/common/requireconfig.js', '/common/requireconfig.js',
'/common/sframe-common-outer.js' '/common/sframe-common-outer.js'
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { ], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
nThen(function (waitFor) { nThen(function (waitFor) {
$(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var req = { var req = {
cfg: requireConfig, cfg: requireConfig,
@ -19,7 +19,7 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
$('#sbox-iframe').attr('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/todo/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/todo/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));
@ -36,12 +36,12 @@ define([
}; };
window.addEventListener('message', onMsg); window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) { }).nThen(function (/*waitFor*/) {
var getSecrets = function (Cryptpad, Utils) { var getSecrets = function (Cryptpad, Utils, cb) {
var proxy = Cryptpad.getProxy(); Cryptpad.getTodoHash(function (hash) {
var hash = proxy.todo || Utils.Hash.createRandomHash(); var nHash = hash || Utils.Hash.createRandomHash();
if (!proxy.todo) { proxy.todo = hash; } if (!hash) { Cryptpad.setTodoHash(nHash); }
cb(null, Utils.Hash.getSecrets('todo', hash));
return Utils.Hash.getSecrets('todo', hash); });
}; };
SFCommonO.start({ SFCommonO.start({
getSecrets: getSecrets, getSecrets: getSecrets,

View file

@ -2,15 +2,15 @@
define([ define([
'/bower_components/nthen/index.js', '/bower_components/nthen/index.js',
'/api/config', '/api/config',
'jquery', '/common/dom-ready.js',
'/common/requireconfig.js', '/common/requireconfig.js',
'/common/sframe-common-outer.js' '/common/sframe-common-outer.js'
], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { ], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig(); var requireConfig = RequireConfig();
// Loaded in load #2 // Loaded in load #2
nThen(function (waitFor) { nThen(function (waitFor) {
$(waitFor()); DomReady.onReady(waitFor());
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var req = { var req = {
cfg: requireConfig, cfg: requireConfig,
@ -19,7 +19,7 @@ define([
}; };
window.rc = requireConfig; window.rc = requireConfig;
window.apiconf = ApiConfig; window.apiconf = ApiConfig;
$('#sbox-iframe').attr('src', document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/whiteboard/inner.html?' + requireConfig.urlArgs + ApiConfig.httpSafeOrigin + '/whiteboard/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req))); '#' + encodeURIComponent(JSON.stringify(req)));