diff --git a/.jshintrc b/.jshintrc index aeddcda04..c95e9dbc4 100644 --- a/.jshintrc +++ b/.jshintrc @@ -7,7 +7,6 @@ "iterator": true, "latedef": true, "nocomma": true, - "notypeof": true, "shadow": false, "undef": true, "unused": true, diff --git a/bower.json b/bower.json index 10ab90388..fad124ba9 100644 --- a/bower.json +++ b/bower.json @@ -44,7 +44,8 @@ "open-sans-fontface": "^1.4.2", "bootstrap-tokenfield": "^0.12.1", "localforage": "^1.5.2", - "html2canvas": "^0.4.1" + "html2canvas": "^0.4.1", + "croppie": "^2.5.0" }, "resolutions": { "bootstrap": "v4.0.0-alpha.6" diff --git a/config.example.js b/config.example.js index ce38b5c3a..3a097eb87 100644 --- a/config.example.js +++ b/config.example.js @@ -159,6 +159,24 @@ module.exports = { */ 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 * the people who have accounts. This check-in will also send the version of your CryptPad diff --git a/customize.dist/404.html b/customize.dist/404.html new file mode 100644 index 000000000..9298a5377 --- /dev/null +++ b/customize.dist/404.html @@ -0,0 +1,16 @@ + + + + + CryptPad: Zero Knowledge, Collaborative Real Time Editing + + + + + + + diff --git a/customize.dist/four-oh-four.js b/customize.dist/four-oh-four.js new file mode 100644 index 000000000..ab19f577d --- /dev/null +++ b/customize.dist/four-oh-four.js @@ -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'); + })(); +}); + diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 7cb43762d..7e84dd01d 100644 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -12,7 +12,7 @@ var map = { var messages = {}; 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 getLanguage = messages._getLanguage = function () { if (window.cryptpadLanguage) { return window.cryptpadLanguage; } @@ -24,19 +24,17 @@ var getLanguage = messages._getLanguage = function () { }; 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'); } -define(req, function($, Default, Language) { +define(req, function(Util, Default, Language) { map.en = 'English'; var defaultLanguage = 'en'; - if (!Language || language === defaultLanguage || !map[language]) { - messages = $.extend(true, messages, Default); - } - else { + Util.extend(messages, Default); + if (Language && language !== defaultLanguage) { // Add the translated keys to the returned object - messages = $.extend(true, messages, Default, Language); + Util.extend(messages, Language); } messages._languages = map; diff --git a/customize.dist/src/less2/404.less b/customize.dist/src/less2/404.less new file mode 100644 index 000000000..ca1c3503e --- /dev/null +++ b/customize.dist/src/less2/404.less @@ -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"; } + diff --git a/customize.dist/src/less2/pages/page-404.less b/customize.dist/src/less2/pages/page-404.less new file mode 100644 index 000000000..6c67732fd --- /dev/null +++ b/customize.dist/src/less2/pages/page-404.less @@ -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; + } + } +} + diff --git a/customize.dist/translations/messages.fr.js b/customize.dist/translations/messages.fr.js index 7a0ee42d4..0ba3413a3 100644 --- a/customize.dist/translations/messages.fr.js +++ b/customize.dist/translations/messages.fr.js @@ -298,6 +298,8 @@ define(function () { out.profile_namePlaceholder = 'Nom ou pseudo pour le profil'; out.profile_avatar = "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_register = "Vous devez vous inscrire pour pouvoir 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_3rdparties = "Nous ne fournissons aucune donnée individuelle à des tierces parties à moins d'y être contraints par la loi."; - // BottomBar.html - - out.bottom_france = 'Fait avec amour en France'; - out.bottom_support = 'Un projet XWiki SAS Labs avec le soutien de OpenPaaS-ng'; + // 404 page + out.four04_pageNotFound = "Nous n'avons pas trouvé la page que vous cherchez."; // Header.html diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js index 36a94307a..90ce1871e 100644 --- a/customize.dist/translations/messages.js +++ b/customize.dist/translations/messages.js @@ -302,6 +302,8 @@ define(function () { out.profile_namePlaceholder = 'Name displayed in your profile'; out.profile_avatar = "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_register = "You have to sign up to 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_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 - out.bottom_france = 'Made with love in France'; - out.bottom_support = 'An XWiki SAS Labs Project with the support of OpenPaaS-ng'; + //out.bottom_france = 'Made with love in France'; + //out.bottom_support = 'An XWiki SAS Labs Project with the support of OpenPaaS-ng'; // Header.html diff --git a/rpc.js b/rpc.js index fc87bde58..60bc00ac3 100644 --- a/rpc.js +++ b/rpc.js @@ -427,6 +427,28 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/) "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) { if (!('' + response.statusCode).match(/^2\d\d$/)) { return void cb('SERVER ERROR ' + response.statusCode); @@ -441,6 +463,11 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/) try { var json = JSON.parse(str); limits = json; + Object.keys(customLimits).forEach(function (k) { + if (!isLimit(customLimits[k])) { return; } + limits[k] = customLimits[k]; + }); + var l; if (userId) { var limit = limits[userId]; diff --git a/server.js b/server.js index da847ac31..4c64965fe 100644 --- a/server.js +++ b/server.js @@ -157,6 +157,22 @@ app.get('/api/config', function(req, res){ ].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); httpServer.listen(config.httpPort,config.httpAddress,function(){ diff --git a/www/assert/main.js b/www/assert/main.js index 968200dc9..e88e13fd1 100644 --- a/www/assert/main.js +++ b/www/assert/main.js @@ -5,9 +5,11 @@ define([ '/drive/tests.js', '/common/test.js', '/common/common-hash.js', + '/common/common-util.js', '/common/common-thumbnail.js', + '/common/wire.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.Sortify = Sortify; @@ -30,7 +32,7 @@ define([ ASSERTS.forEach(function (f, index) { f(function (err) { - console.log("test " + index); + //console.log("test " + index); done(err, index); }, index); }); @@ -235,6 +237,54 @@ define([ return cb(true); }, "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) { var getBlob = function (url, cb) { diff --git a/www/auth/main.js b/www/auth/main.js index 5f1a5a333..a45751dcb 100644 --- a/www/auth/main.js +++ b/www/auth/main.js @@ -4,8 +4,9 @@ define([ '/common/common-constants.js', '/common/outer/local-store.js', '/common/test.js', + '/bower_components/nthen/index.js', '/bower_components/tweetnacl/nacl-fast.min.js' -], function ($, Cryptpad, Constants, LocalStore, Test) { +], function ($, Cryptpad, Constants, LocalStore, Test, nThen) { var Nacl = window.nacl; var signMsg = function (msg, privKey) { @@ -25,11 +26,18 @@ define([ localStorage[Constants.userHashKey] = localStorage[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'); Test(function () { // This is only here to maybe trigger an error. - window.drive = Cryptpad.getStore().getProxy().proxy['drive']; + window.drive = proxy['drive']; Test.passed(); }); $(window).on("message", function (jqe) { @@ -46,7 +54,6 @@ define([ } else if (!LocalStore.isLoggedIn()) { ret.error = "NOT_LOGGED_IN"; } else { - var proxy = Cryptpad.getStore().getProxy().proxy; var sig = signMsg(data.data, proxy.edPrivate); ret.res = { uname: proxy.login_name, diff --git a/www/common/common-constants.js b/www/common/common-constants.js index 76c70d103..044319069 100644 --- a/www/common/common-constants.js +++ b/www/common/common-constants.js @@ -10,5 +10,6 @@ define(function () { displayNameKey: 'cryptpad.username', oldStorageKey: 'CryptPad_RECENTPADS', storageKey: 'filesData', + tokenKey: 'loginToken', }; }); diff --git a/www/common/common-language.js b/www/common/common-language.js index 1af06df33..8b27a64bf 100644 --- a/www/common/common-language.js +++ b/www/common/common-language.js @@ -10,7 +10,6 @@ define([ // Add handler to the language selector Msg.setLanguage = function (l, sframeChan, cb) { - console.log(sframeChan); if (sframeChan) { // We're in the sandbox sframeChan.query("Q_LANGUAGE_SET", l, cb); diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index da5ac004a..13d132219 100644 --- a/www/common/common-messaging.js +++ b/www/common/common-messaging.js @@ -1,15 +1,12 @@ define([ - 'jquery', '/bower_components/chainpad-crypto/crypto.js', - '/common/curve.js', '/common/common-hash.js', '/common/common-util.js', '/common/common-constants.js', '/customize/messages.js', - '/bower_components/marked/marked.min.js', '/common/common-realtime.js', -], function ($, Crypto, Curve, Hash, Util, Constants, Messages, Marked, Realtime) { +], function (Crypto, Hash, Util, Constants, Messages, Realtime) { var Msg = { inputs: [], }; @@ -51,9 +48,8 @@ define([ }); }; - Msg.getFriendChannelsList = function (common) { + Msg.getFriendChannelsList = function (proxy) { var list = []; - var proxy = common.getProxy(); eachFriend(proxy.friends, function (friend) { list.push(friend.channel); }); @@ -61,7 +57,7 @@ define([ }; // TODO make this internal to the messenger - var channels = Msg.channels = window.channels = {}; + var channels = Msg.channels = {}; Msg.getLatestMessages = function () { Object.keys(channels).forEach(function (id) { @@ -74,8 +70,8 @@ define([ // Invitation // FIXME there are too many functions with this name - var addToFriendList = Msg.addToFriendList = function (common, data, cb) { - var proxy = common.getProxy(); + var addToFriendList = Msg.addToFriendList = function (cfg, data, cb) { + var proxy = cfg.proxy; var friends = getFriendList(proxy); var pubKey = data.curvePublic; // todo validata data @@ -83,19 +79,19 @@ define([ friends[pubKey] = data; - Realtime.whenRealtimeSyncs(common.getRealtime(), function () { + Realtime.whenRealtimeSyncs(cfg.realtime, function () { cb(); - common.pinPads([data.channel], function (e) { - if (e) { console.error(e); } + cfg.pinPads([data.channel], function (res) { + if (res.error) { console.error(res.error); } }); }); - common.changeDisplayName(proxy[Constants.displayNameKey]); + cfg.updateMetadata(); }; /* Used to accept friend requests within apps other than /contacts/ */ - Msg.addDirectMessageHandler = function (common) { - var network = common.getNetwork(); - var proxy = common.getProxy(); + Msg.addDirectMessageHandler = function (cfg) { + var network = cfg.network; + var proxy = cfg.proxy; if (!network) { return void console.error('Network not ready'); } network.on('message', function (message, sender) { var msg; @@ -138,8 +134,7 @@ define([ var confirmMsg = Messages._getKey('contacts_request', [ Util.fixHTML(msgData.displayName) ]); - common.onFriendRequest(confirmMsg, todo); - //UI.confirm(confirmMsg, todo, null, true); + cfg.friendRequest(confirmMsg, todo); return; } if (msg[0] === "FRIEND_REQ_OK") { @@ -147,14 +142,14 @@ define([ if (idx !== -1) { pendingRequests.splice(idx, 1); } // FIXME clarify this function's name - addToFriendList(common, msgData, function (err) { + addToFriendList(cfg, msgData, function (err) { if (err) { - return void common.onFriendComplete({ + return void cfg.friendComplete({ logText: Messages.contacts_addError, netfluxId: sender }); } - common.onFriendComplete({ + cfg.friendComplete({ logText: Messages.contacts_added, netfluxId: sender }); @@ -167,24 +162,24 @@ define([ if (msg[0] === "FRIEND_REQ_NOK") { var i = pendingRequests.indexOf(sender); if (i !== -1) { pendingRequests.splice(i, 1); } - common.onFriendComplete({ + cfg.friendComplete({ logText: Messages.contacts_rejected, netfluxId: sender }); - common.changeDisplayName(proxy[Constants.displayNameKey]); + cfg.updateMetadata(); return; } if (msg[0] === "FRIEND_REQ_ACK") { var data = pending[sender]; if (!data) { return; } - addToFriendList(common, data, function (err) { + addToFriendList(cfg, data, function (err) { if (err) { - return void common.onFriendComplete({ + return void cfg.friendComplete({ logText: Messages.contacts_addError, netfluxId: sender }); } - common.onFriendComplete({ + cfg.friendComplete({ logText: Messages.contacts_added, netfluxId: sender }); @@ -198,17 +193,14 @@ define([ }); }; - Msg.getPending = function () { - return pendingRequests; - }; - - Msg.inviteFromUserlist = function (common, netfluxId) { - var network = common.getNetwork(); - var parsed = Hash.parsePadUrl(window.location.href); + Msg.inviteFromUserlist = function (cfg, data, cb) { + var network = cfg.network; + var netfluxId = data.netfluxId; + var parsed = Hash.parsePadUrl(data.href); if (!parsed.hashData) { return; } // Message var chan = parsed.hashData.channel; - var myData = createData(common.getProxy()); + var myData = createData(cfg.proxy); var msg = ["FRIEND_REQ", chan, myData]; // Encryption var keyStr = parsed.hashData.key; @@ -218,12 +210,10 @@ define([ // Send encrypted message if (pendingRequests.indexOf(netfluxId) === -1) { pendingRequests.push(netfluxId); - var proxy = common.getProxy(); - // this redraws the userlist after a change has occurred - // TODO rename this function to reflect its purpose - common.changeDisplayName(proxy[Constants.displayNameKey]); + cfg.updateMetadata(); // redraws the userlist in pad } network.sendto(netfluxId, msgStr); + cb(); }; return Msg; diff --git a/www/common/common-messenger.js b/www/common/common-messenger.js index 406b27482..cd0a5626d 100644 --- a/www/common/common-messenger.js +++ b/www/common/common-messenger.js @@ -1,11 +1,11 @@ define([ - 'jquery', '/bower_components/chainpad-crypto/crypto.js', '/common/curve.js', '/common/common-hash.js', '/common/common-util.js', '/common/common-realtime.js', -], function ($, Crypto, Curve, Hash, Util, Realtime) { + '/common/common-constants.js', +], function (Crypto, Curve, Hash, Util, Realtime, Constants) { 'use strict'; var Msg = { inputs: [], @@ -28,7 +28,7 @@ define([ var createData = Msg.createData = function (proxy, hash) { return { channel: hash || Hash.createChannelId(), - displayName: proxy['cryptpad.username'], + displayName: proxy[Constants.displayNameKey], profile: proxy.profile && proxy.profile.view, edPublic: proxy.edPublic, curvePublic: proxy.curvePublic, @@ -56,7 +56,7 @@ define([ }); }; - Msg.messenger = function (common) { + Msg.messenger = function (store) { var messenger = { handlers: { message: [], @@ -89,9 +89,9 @@ define([ var joining = {}; // declare common variables - var network = common.getNetwork(); - var proxy = common.getProxy(); - var realtime = common.getRealtime(); + var network = store.network; + var proxy = store.proxy; + var realtime = store.realtime; Msg.hk = network.historyKeeper; var friends = getFriendList(proxy); @@ -484,7 +484,7 @@ define([ }; var msg = ['GET_HISTORY', chan.id, cfg]; network.sendto(network.historyKeeper, JSON.stringify(msg)) - .then($.noop, function (err) { + .then(function () {}, function (err) { throw new Error(err); }); }; @@ -629,14 +629,7 @@ define([ messenger.getMyInfo = function (cb) { cb(void 0, { curvePublic: proxy.curvePublic, - displayName: common.getDisplayName(), - }); - }; - - messenger.clearOwnedChannel = function (channel, cb) { - common.clearOwnedChannel(channel, function (e) { - if (e) { return void cb(e); } - cb(); + displayName: proxy[Constants.displayNameKey] }); }; diff --git a/www/common/common-realtime.js b/www/common/common-realtime.js index 6480d7e6b..21c0728e2 100644 --- a/www/common/common-realtime.js +++ b/www/common/common-realtime.js @@ -1,18 +1,6 @@ -define([ - '/customize/application_config.js', - '/customize/messages.js', - '/common/common-interface.js', -], function (AppConfig, Messages, UI) { +define([], function () { 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... */ @@ -20,7 +8,7 @@ define([ if (typeof(realtime.getAuthDoc) !== 'function') { return void console.error('improper use of this function'); } - window.setTimeout(function () { + setTimeout(function () { if (realtime.getAuthDoc() === realtime.getUserDoc()) { return void cb(); } else { @@ -29,36 +17,5 @@ define([ }, 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; }); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 57392a5cd..c060b0cdc 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -98,7 +98,13 @@ define([ target: data.target }; 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; } data.FM.handleFile(file, ev); @@ -571,12 +577,7 @@ define([ // getPinnedUsage updates common.account.usage, and other values // so we can just use those and only check for errors var $container = $('', {'class':'cp-limit-container'}); - var todo; - var updateUsage = Util.notAgainForAnother(function () { - common.getPinUsage(todo); - }, LIMIT_REFRESH_RATE); - - todo = function (err, data) { + var todo = function (err, data) { if (err) { return void console.error(err); } var usage = data.usage; @@ -645,6 +646,10 @@ define([ $limit.append($usage).append($text); }; + var updateUsage = Util.notAgainForAnother(function () { + common.getPinUsage(todo); + }, LIMIT_REFRESH_RATE); + setInterval(function () { updateUsage(); }, LIMIT_REFRESH_RATE * 3); diff --git a/www/common/common-util.js b/www/common/common-util.js index 759730663..030536751 100644 --- a/www/common/common-util.js +++ b/www/common/common-util.js @@ -209,6 +209,43 @@ define([], function () { 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; }); }(self)); diff --git a/www/common/cryptget.js b/www/common/cryptget.js index 7a663480e..686c69884 100644 --- a/www/common/cryptget.js +++ b/www/common/cryptget.js @@ -73,12 +73,12 @@ define([ realtime.contentUpdate(doc); - var to = window.setTimeout(function () { + var to = setTimeout(function () { cb(new Error("Timeout")); }, 5000); Realtime.whenRealtimeSyncs(realtime, function () { - window.clearTimeout(to); + clearTimeout(to); realtime.abort(); finish(Session, void 0); }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 181f6b7ae..68a17a5c2 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1,22 +1,20 @@ define([ - 'jquery', '/api/config', '/customize/messages.js', - '/common/fsStore.js', '/common/common-util.js', '/common/common-hash.js', '/common/common-messaging.js', '/common/common-realtime.js', - '/common/common-language.js', '/common/common-constants.js', '/common/common-feedback.js', '/common/outer/local-store.js', + '/common/outer/store-rpc.js', '/common/pinpad.js', '/customize/application_config.js', '/bower_components/nthen/index.js', -], function ($, Config, Messages, Store, Util, Hash, - Messaging, Realtime, Language, Constants, Feedback, LocalStore, +], function (Config, Messages, Util, Hash, + Messaging, Realtime, Constants, Feedback, LocalStore, AStore, Pinpad, AppConfig, Nthen) { /* This file exposes functionality which is specific to Cryptpad, but not to @@ -25,6 +23,18 @@ define([ Additionally, there is some basic functionality for import/export. */ + var postMessage = function (cmd, data, cb) { + setTimeout(function () { + AStore.query(cmd, data, cb); + }); + }; + var tryParsing = function (x) { + try { return JSON.parse(x); } + catch (e) { + console.error(e); + return null; + } + }; var origin = encodeURIComponent(window.location.hostname); var common = window.Cryptpad = { @@ -36,252 +46,353 @@ define([ var PINNING_ENABLED = AppConfig.enablePinning; - var store; - var rpc; - var anon_rpc; + // COMMON + common.getLanguage = function () { + return Messages._languageUsed; + }; + common.setLanguage = function (l, cb) { + var LS_LANG = "CRYPTPAD_LANG"; + localStorage.setItem(LS_LANG, l); + cb(); + }; + - var getStore = common.getStore = function () { - if (store) { return store; } - throw new Error("Store is not ready!"); + // RESTRICTED + // Settings only + common.getUserObject = function (cb) { + postMessage("GET", [], function (obj) { + cb(obj); + }); }; - var getProxy = common.getProxy = function () { - if (store && store.getProxy()) { - return store.getProxy().proxy; - } + common.resetDrive = function (cb) { + postMessage("RESET_DRIVE", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); + }); }; - common.getFO = function () { - if (store && store.getProxy()) { - return store.getProxy().fo; - } + common.logoutFromAll = function (cb) { + var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); + localStorage.setItem(Constants.tokenKey, token); + postMessage("SET", { + key: [Constants.tokenKey], + value: token + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(); + }); }; - var getNetwork = common.getNetwork = function () { - if (store) { - if (store.getProxy() && store.getProxy().info) { - return store.getProxy().info.network; - } - } - return; + // Settings and auth + common.getUserObject = function (cb) { + postMessage("GET", [], function (obj) { + cb(obj); + }); }; - - // REFACTOR pull language directly - common.getLanguage = function () { - return Messages._languageUsed; + // Settings and ready + common.mergeAnonDrive = function (cb) { + var data = { + anonHash: LocalStore.getFSHash() + }; + postMessage("MIGRATE_ANON_DRIVE", data, cb); }; - common.setLanguage = function (l, cb) { - Language.setLanguage(l, null, cb); + // Profile + common.getProfileEditUrl = function (cb) { + postMessage("GET", ['profile', 'edit'], function (obj) { + cb(obj); + }); }; - - // REAFCTOR store.getProfile should be store.get(['profile']) - common.getProfileUrl = function () { - if (store && store.getProfile()) { - return store.getProfile().view; - } + common.setNewProfile = function (profile) { + postMessage("SET", { + key: ['profile'], + value: profile + }, function () {}); }; - common.getAvatarUrl = function () { - if (store && store.getProfile()) { - return store.getProfile().avatar; - } + common.setAvatar = function (data, cb) { + var postData = { + key: ['profile', 'avatar'] + }; + // If we don't have "data", it means we want to remove the avatar and we should not have a + // "postData.value", even set to undefined (JSON.stringify transforms undefined to null) + if (data) { postData.value = data; } + postMessage("SET", postData, cb); + }; + // Todo + common.getTodoHash = function (cb) { + postMessage("GET", ['todo'], function (obj) { + cb(obj); + }); }; - common.getDisplayName = function (cb) { - var name; - if (getProxy()) { - name = getProxy()[Constants.displayNameKey]; - } - name = name || ''; - if (typeof cb === "function") { cb(null, name); } - return name; + common.setTodoHash = function (hash) { + postMessage("SET", { + key: ['todo'], + value: hash + }, function () {}); }; - common.getUid = function () { - if (store && store.getProxy() && store.getProxy().proxy) { - return store.getProxy().proxy.uid; - } + + // RPC + common.pinPads = function (pads, cb) { + postMessage("PIN_PADS", pads, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj.hash); + }); }; - var getRealtime = common.getRealtime = function () { - if (store && store.getProxy() && store.getProxy().info) { - return store.getProxy().info.realtime; - } - return; + common.unpinPads = function (pads, cb) { + postMessage("UNPIN_PADS", pads, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj.hash); + }); }; - common.hasSigningKeys = function (proxy) { - return typeof(proxy) === 'object' && - typeof(proxy.edPrivate) === 'string' && - typeof(proxy.edPublic) === 'string'; + common.getPinnedUsage = function (cb) { + postMessage("GET_PINNED_USAGE", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(null, obj.bytes); + }); }; - common.hasCurveKeys = function (proxy) { - return typeof(proxy) === 'object' && - typeof(proxy.curvePrivate) === 'string' && - typeof(proxy.curvePublic) === 'string'; + common.updatePinLimit = function (cb) { + postMessage("UPDATE_PIN_LIMIT", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(undefined, obj.limit, obj.plan, obj.note); + }); }; - common.getPublicKeys = function (proxy) { - proxy = proxy || common.getProxy(); - if (!proxy || !proxy.edPublic || !proxy.curvePublic) { return; } - return { - curve: proxy.curvePublic, - ed: proxy.edPublic, - }; + common.getPinLimit = function (cb) { + postMessage("GET_PIN_LIMIT", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(undefined, obj.limit, obj.plan, obj.note); + }); }; - var makePad = common.makePad = function (href, title) { - var now = +new Date(); - return { - href: href, - atime: now, - ctime: now, - title: title || Hash.getDefaultName(Hash.parsePadUrl(href)), + common.isOverPinLimit = function (cb) { + if (!LocalStore.isLoggedIn()) { return void cb(null, false); } + var usage; + var andThen = function (e, limit, plan) { + if (e) { return void cb(e); } + var data = {usage: usage, limit: limit, plan: plan}; + if (usage > limit) { + return void cb (null, true, data); + } + return void cb (null, false, data); }; + var todo = function (e, used) { + if (e) { return void cb(e); } + usage = used; + common.getPinLimit(andThen); + }; + common.getPinnedUsage(todo); }; - // STORAGE - common.setPadAttribute = function (attr, value, cb, href) { - href = Hash.getRelativeHref(href || window.location.href); - getStore().setPadAttribute(href, attr, value, cb); + common.clearOwnedChannel = function (channel, cb) { + postMessage("CLEAR_OWNED_CHANNEL", channel, cb); }; - common.setDisplayName = function (value, cb) { - if (getProxy()) { - getProxy()[Constants.displayNameKey] = value; - } - if (typeof cb === "function") { Realtime.whenRealtimeSyncs(getRealtime(), cb); } + + common.uploadComplete = function (cb) { + postMessage("UPLOAD_COMPLETE", null, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); }; - common.setAttribute = function (attr, value, cb) { - getStore().setAttribute(attr, value, function (err, data) { - if (cb) { cb(err, data); } + + common.uploadStatus = function (size, cb) { + postMessage("UPLOAD_STATUS", {size: size}, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); }); }; - // STORAGE - common.getPadAttribute = function (attr, cb) { - var href = Hash.getRelativeHref(window.location.href); - getStore().getPadAttribute(href, attr, cb); + common.uploadCancel = function (cb) { + postMessage("UPLOAD_CANCEL", null, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); }; - common.getAttribute = function (attr, cb) { - getStore().getAttribute(attr, function (err, data) { - cb(err, data); + + common.uploadChunk = function (data, cb) { + postMessage("UPLOAD_CHUNK", {chunk: data}, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); }); }; + // ANON RPC - /* this returns a reference to your proxy. changing it will change your drive. - */ - var getFileEntry = common.getFileEntry = function (href, cb) { - if (typeof(cb) !== 'function') { return; } - var store = getStore(); - if (!store) { return void cb('NO_STORE'); } - href = href || (window.location.pathname + window.location.hash); - var id = store.getIdFromHref(href); - if (!id) { return void cb('NO_ID'); } - var entry = Util.find(getProxy(), [ - 'drive', - 'filesData', - id - ]); - cb(void 0, entry); + // SFRAME: talk to anon_rpc from the iframe + common.anonRpcMsg = function (msg, data, cb) { + if (!msg) { return; } + postMessage("ANON_RPC_MESSAGE", { + msg: msg, + data: data + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); }; - common.resetTags = function (href, tags, cb) { - cb = cb || $.noop; - if (!Array.isArray(tags)) { return void cb('INVALID_TAGS'); } - getFileEntry(href, function (e, entry) { - if (e) { return void cb(e); } - if (!entry) { cb('NO_ENTRY'); } - entry.tags = tags.slice(); + common.getFileSize = function (href, cb) { + postMessage("GET_FILE_SIZE", {href: href}, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(undefined, obj.size); + }); + }; + + common.getMultipleFileSize = function (files, cb) { + postMessage("GET_MULTIPLE_FILE_SIZE", {files:files}, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(undefined, obj.size); + }); + }; + + // Store + + + + common.getMetadata = function (cb) { + postMessage("GET_METADATA", null, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); + }; + + common.setDisplayName = function (value, cb) { + postMessage("SET_DISPLAY_NAME", value, cb); + }; + + common.setPadAttribute = function (attr, value, cb, href) { + href = Hash.getRelativeHref(href || window.location.href); + postMessage("SET_PAD_ATTRIBUTE", { + href: href, + attr: attr, + value: value + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } cb(); }); }; + common.getPadAttribute = function (attr, cb) { + var href = Hash.getRelativeHref(window.location.href); + postMessage("GET_PAD_ATTRIBUTE", { + href: href, + attr: attr, + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); + }; + common.setAttribute = function (attr, value, cb) { + postMessage("SET_ATTRIBUTE", { + attr: attr, + value: value + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(); + }); + }; + common.getAttribute = function (attr, cb) { + postMessage("GET_ATTRIBUTE", { + attr: attr + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); + }; + // Tags + common.resetTags = function (href, tags, cb) { + // set pad attribute + cb = cb || function () {}; + if (!Array.isArray(tags)) { return void cb('INVALID_TAGS'); } + common.setPadAttribute('tags', tags.slice(), cb, href); + }; common.tagPad = function (href, tag, cb) { if (typeof(cb) !== 'function') { return void console.error('EXPECTED_CALLBACK'); } if (typeof(tag) !== 'string') { return void cb('INVALID_TAG'); } - getFileEntry(href, function (e, entry) { + common.getPadAttribute('tags', function (e, tags) { if (e) { return void cb(e); } - if (!entry) { cb('NO_ENTRY'); } - if (!entry.tags) { - entry.tags = [tag]; - } else if (entry.tags.indexOf(tag) === -1) { - entry.tags.push(tag); + var newTags; + if (!tags) { + newTags = [tag]; + } else if (tags.indexOf(tag) === -1) { + newTags = tags.slice(); + newTags.push(tag); } - cb(); - }); + common.setPadAttribute('tags', newTags, cb, href); + }, href); }; - common.untagPad = function (href, tag, cb) { if (typeof(cb) !== 'function') { return void console.error('EXPECTED_CALLBACK'); } if (typeof(tag) !== 'string') { return void cb('INVALID_TAG'); } - getFileEntry(href, function (e, entry) { + common.getPadAttribute('tags', function (e, tags) { if (e) { return void cb(e); } - if (!entry) { cb('NO_ENTRY'); } - if (!entry.tags) { return void cb(); } - var idx = entry.tags.indexOf(tag); + if (!tags) { return void cb(); } + var idx = tags.indexOf(tag); if (idx === -1) { return void cb(); } - entry.tags.splice(idx, 1); - cb(); - }); + var newTags = tags.slice(); + newTags.splice(idx, 1); + common.setPadAttribute('tags', newTags, cb, href); + }, href); }; - common.getPadTags = function (href, cb) { if (typeof(cb) !== 'function') { return; } - getFileEntry(href, function (e, entry) { - if (entry) { - return void cb(void 0, entry.tags? - JSON.parse(JSON.stringify(entry.tags)): []); - } - return cb('NO_ENTRY'); - }); + common.getPadAttribute('tags', function (e, tags) { + if (e) { return void cb(e); } + cb(void 0, tags ? tags.slice() : []); + }, href); }; - common.listAllTags = function (cb) { - var all = []; - var proxy = getProxy(); - var files = Util.find(proxy, ['drive', 'filesData']); - - if (typeof(files) !== 'object') { return cb('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); - } - }); + postMessage("LIST_ALL_TAGS", null, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(void 0, obj); }); - cb(void 0, all); }; // STORAGE - TEMPLATES - var listTemplates = common.listTemplates = function (type) { - var allTemplates = getStore().listTemplates(); - if (!type) { return allTemplates; } - - var templates = allTemplates.filter(function (f) { - var parsed = Hash.parsePadUrl(f.href); - return parsed.type === type; + common.listTemplates = function (type, cb) { + postMessage("GET_TEMPLATES", null, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + if (!Array.isArray(obj)) { return void cb ('NOT_AN_ARRAY'); } + if (!type) { return void cb(null, obj); } + + var templates = obj.filter(function (f) { + var parsed = Hash.parsePadUrl(f.href); + return parsed.type === type; + }); + cb(null, templates); }); - return templates; }; - common.addTemplate = function (data) { - getStore().pushData(data, function (e, id) { - if (e) { return void console.error("Error while adding a template:", e); } // TODO LIMIT - getStore().addPad(id, ['template']); + + common.saveAsTemplate = function (Cryptput, data, cb) { + var p = Hash.parsePadUrl(window.location.href); + if (!p.type) { return; } + var hash = Hash.createRandomHash(); + var href = '/' + p.type + '/#' + hash; + Cryptput(hash, data.toSave, function (e) { + if (e) { throw new Error(e); } + postMessage("ADD_PAD", { + href: href, + title: data.title, + path: ['template'] + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(); + }); }); }; - common.isTemplate = function (href) { + common.isTemplate = function (href, cb) { var rhref = Hash.getRelativeHref(href); - var templates = listTemplates(); - return templates.some(function (t) { - return t.href === rhref; + common.listTemplates(null, function (err, templates) { + cb(void 0, templates.some(function (t) { + return t.href === rhref; + })); }); }; - // Secure iframes common.useTemplate = function (href, Crypt, cb) { var parsed = Hash.parsePadUrl(href); if(!parsed) { throw new Error("Cannot get template hash"); } @@ -292,466 +403,111 @@ define([ }); }; - // STORAGE - /* fetch and migrate your pad history from the store */ - var getRecentPads = common.getRecentPads = function (cb) { - getStore().getDrive('filesData', function (err, recentPads) { - if (typeof(recentPads) === "object") { - cb(void 0, recentPads); - return; - } - cb(void 0, {}); - }); - }; - - // STORAGE: Display Name - common.getLastName = common.getDisplayName; - /* function (cb) { - common.getDisplayName(function (err, userName) { - cb(err, userName); - }); - };*/ - var _onDisplayNameChanged = []; - common.onDisplayNameChanged = function (h) { - if (typeof(h) !== "function") { return; } - if (_onDisplayNameChanged.indexOf(h) !== -1) { return; } - _onDisplayNameChanged.push(h); - }; - common.changeDisplayName = function (newName, isLocal) { - _onDisplayNameChanged.forEach(function (h) { - h(newName, isLocal); - }); - }; - - // STORAGE - common.forgetPad = function (href, cb) { - if (typeof(getStore().forgetPad) === "function") { - getStore().forgetPad(Hash.getRelativeHref(href), cb); - return; - } - cb ("store.forgetPad is not a function"); + // Forget button + common.moveToTrash = function (cb, href) { + href = href || window.location.href; + postMessage("MOVE_TO_TRASH", { href: href }, cb); }; - common.setPadTitle = function (name, padHref, cb) { - var href = typeof padHref === "string" ? padHref : window.location.href; + // When opening a new pad or renaming it, store the new title + common.setPadTitle = function (title, padHref, path, cb) { + var href = padHref || window.location.href; var parsed = Hash.parsePadUrl(href); if (!parsed.hash) { return; } href = parsed.getUrl({present: parsed.present}); - //href = Hash.getRelativeHref(href); - // getRecentPads return the array from the drive, not a copy - // We don't have to call "set..." at the end, everything is stored with listmap - getRecentPads(function (err, recent) { - if (err) { - cb(err); - return; - } - - var updateWeaker = []; - var contains; - Object.keys(recent).forEach(function (id) { - var pad = recent[id]; - var p = Hash.parsePadUrl(pad.href); - - if (p.type !== parsed.type) { return pad; } - - var shouldUpdate = p.hash.replace(/\/$/, '') === parsed.hash.replace(/\/$/, ''); - - // Version 1 : we have up to 4 differents hash for 1 pad, keep the strongest : - // Edit > Edit (present) > View > View (present) - var pHash = p.hashData; - var parsedHash = parsed.hashData; - - if (!pHash) { return; } // We may have a corrupted pad in our storage, abort here in that case - - if (!shouldUpdate && pHash.version === 1 && parsedHash.version === 1 && pHash.channel === parsedHash.channel) { - if (pHash.mode === 'view' && parsedHash.mode === 'edit') { shouldUpdate = true; } - else if (pHash.mode === parsedHash.mode && pHash.present) { shouldUpdate = true; } - else { - // Editing a "weaker" version of a stored hash : update the date and do not push the current hash - pad.atime = +new Date(); - contains = true; - return pad; - } - } - - if (shouldUpdate) { - contains = true; - // update the atime - pad.atime = +new Date(); - - // set the name - pad.title = name; - - // If we now have a stronger version of a stored href, replace the weaker one by the strong one - if (pad && pad.href && href !== pad.href) { - updateWeaker.push({ - o: pad.href, - n: href - }); - } - pad.href = href; - } - return pad; - }); - - if (updateWeaker.length > 0) { - updateWeaker.forEach(function (obj) { - // If we have a stronger url, and if all the occurences of the weaker were - // in the trash, add remove them from the trash and add the stronger in root - getStore().restoreHref(obj.n); - }); - } - if (!contains && href) { - var data = makePad(href, name); - getStore().pushData(data, function (e, id) { - if (e) { - return void cb(e); - } - getStore().addPad(id, common.initialPath); - cb(err, recent); - }); - return; - } - cb(err, recent); - }); - }; - /* - * Buttons - */ - common.renamePad = function (title, href, callback) { if (title === null) { return; } + if (title.trim() === "") { title = Hash.getDefaultName(parsed); } - if (title.trim() === "") { - var parsed = Hash.parsePadUrl(href || window.location.href); - title = Hash.getDefaultName(parsed); - } - - common.setPadTitle(title, href, function (err) { - if (err) { + postMessage("SET_PAD_TITLE", { + href: href, + title: title, + path: path + }, function (obj) { + if (obj && obj.error) { console.log("unable to set pad title"); - console.error(err); - return; + return void cb(obj.error); } - callback(null, title); + cb(); }); }; // Needed for the secure filepicker app common.getSecureFilesList = function (query, cb) { - var store = common.getStore(); - if (!store) { return void cb("Store is not ready"); } - var proxy = store.getProxy(); - var fo = proxy.fo; - 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; - }; - fo.getFiles(where).forEach(function (id) { - var data = fo.getFileData(id); - var parsed = Hash.parsePadUrl(data.href); - if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) - && hashes.indexOf(parsed.hash) === -1) { - if (isFiltered(parsed.type, data)) { return; } - hashes.push(parsed.hash); - list[id] = data; - } + postMessage("GET_SECURE_FILES_LIST", query, function (list) { + cb(void 0, list); }); - cb (null, list); - }; - - var getUserChannelList = common.getUserChannelList = function () { - var store = common.getStore(); - var proxy = store.getProxy(); - var fo = proxy.fo; - - // start with your userHash... - var userHash = localStorage && localStorage[Constants.userHashKey]; - if (!userHash) { return null; } - - var userParsedHash = Hash.parseTypeHash('drive', userHash); - var userChannel = userParsedHash && userParsedHash.channel; - if (!userChannel) { return null; } - - var list = fo.getFiles([fo.FILES_DATA]).map(function (id) { - return Hash.hrefToHexChannelId(fo.getFileData(id).href); - }) - .filter(function (x) { return x; }); - - // Get the avatar - var profile = store.getProfile(); - 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 (getProxy().friends) { - var fList = Messaging.getFriendChannelsList(common); - list = list.concat(fList); - } - - list.push(Util.base64ToHex(userChannel)); - list.sort(); - - return list; - }; - - var getCanonicalChannelList = common.getCanonicalChannelList = function () { - return Util.deduplicateString(getUserChannelList()).sort(); }; - var pinsReady = common.pinsReady = function () { - if (!LocalStore.isLoggedIn()) { - return false; - } - if (!PINNING_ENABLED) { - console.error('[PINNING_DISABLED]'); - return false; - } - if (!rpc) { - console.error('RPC_NOT_READY'); - return false; - } - return true; - }; - - common.arePinsSynced = function (cb) { - if (!pinsReady()) { return void cb ('RPC_NOT_READY'); } - - var list = getCanonicalChannelList(); - var local = Hash.hashChannelList(list); - rpc.getServerHash(function (e, hash) { - if (e) { return void cb(e); } - cb(void 0, hash === local); + // Messaging (manage friends from the userlist) + common.inviteFromUserlist = function (netfluxId, cb) { + postMessage("INVITE_FROM_USERLIST", { + netfluxId: netfluxId, + href: window.location.href + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(); }); }; - common.resetPins = function (cb) { - if (!pinsReady()) { return void cb ('RPC_NOT_READY'); } - - var list = getCanonicalChannelList(); - rpc.reset(list, function (e, hash) { - if (e) { return void cb(e); } - cb(void 0, hash); - }); + // Messenger + var messenger = common.messenger = {}; + messenger.getFriendList = function (cb) { + postMessage("CONTACTS_GET_FRIEND_LIST", null, cb); }; - - common.pinPads = function (pads, cb) { - if (!pinsReady()) { return void cb ('RPC_NOT_READY'); } - if (typeof(cb) !== 'function') { - console.error('expected a callback'); - } - - rpc.pin(pads, function (e, hash) { - if (e) { return void cb(e); } - cb(void 0, hash); - }); + messenger.getMyInfo = function (cb) { + postMessage("CONTACTS_GET_MY_INFO", null, cb); }; - - common.unpinPads = function (pads, cb) { - if (!pinsReady()) { return void cb ('RPC_NOT_READY'); } - - rpc.unpin(pads, function (e, hash) { - if (e) { return void cb(e); } - cb(void 0, hash); - }); + messenger.getFriendInfo = function (curvePublic, cb) { + postMessage("CONTACTS_GET_FRIEND_INFO", curvePublic, cb); }; - - common.getPinnedUsage = function (cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - - rpc.getFileListSize(function (err, bytes) { - if (typeof(bytes) === 'number') { - common.account.usage = bytes; - } - cb(err, bytes); - }); + messenger.removeFriend = function (curvePublic, cb) { + postMessage("CONTACTS_REMOVE_FRIEND", curvePublic, cb); }; - - // SFRAME: talk to anon_rpc from the iframe - common.anonRpcMsg = function (msg, data, cb) { - if (!msg) { return; } - if (!anon_rpc) { return void cb('ANON_RPC_NOT_READY'); } - anon_rpc.send(msg, data, cb); + messenger.openFriendChannel = function (curvePublic, cb) { + postMessage("CONTACTS_OPEN_FRIEND_CHANNEL", curvePublic, cb); }; - - common.getFileSize = function (href, cb) { - if (!anon_rpc) { return void cb('ANON_RPC_NOT_READY'); } - //if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - var channelId = Hash.hrefToHexChannelId(href); - anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) { - if (e) { return void cb(e); } - if (response && response.length && typeof(response[0]) === 'number') { - return void cb(void 0, response[0]); - } else { - cb('INVALID_RESPONSE'); - } - }); + messenger.getFriendStatus = function (curvePublic, cb) { + postMessage("CONTACTS_GET_FRIEND_STATUS", curvePublic, cb); }; - - common.getMultipleFileSize = function (files, cb) { - if (!anon_rpc) { return void cb('ANON_RPC_NOT_READY'); } - if (!Array.isArray(files)) { - return void setTimeout(function () { cb('INVALID_FILE_LIST'); }); - } - - anon_rpc.send('GET_MULTIPLE_FILE_SIZE', files, function (e, res) { - if (e) { return cb(e); } - if (res && res.length && typeof(res[0]) === 'object') { - cb(void 0, res[0]); - } else { - cb('UNEXPECTED_RESPONSE'); - } - }); + messenger.getMoreHistory = function (data, cb) { + postMessage("CONTACTS_GET_MORE_HISTORY", data, cb); }; - - common.updatePinLimit = function (cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - rpc.updatePinLimits(function (e, limit, plan, note) { - if (e) { return cb(e); } - common.account.limit = limit; - common.account.plan = plan; - common.account.note = note; - cb(e, limit, plan, note); - }); + messenger.sendMessage = function (data, cb) { + postMessage("CONTACTS_SEND_MESSAGE", data, cb); }; - - common.getPinLimit = function (cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - - var account = common.account; - - var ALWAYS_REVALIDATE = true; - if (ALWAYS_REVALIDATE || typeof(account.limit) !== 'number' || - typeof(account.plan) !== 'string' || - typeof(account.note) !== 'string') { - return void rpc.getLimit(function (e, limit, plan, note) { - if (e) { return cb(e); } - common.account.limit = limit; - common.account.plan = plan; - common.account.note = note; - cb(void 0, limit, plan, note); - }); - } - - cb(void 0, account.limit, account.plan, account.note); - }; - - common.isOverPinLimit = function (cb) { - if (!LocalStore.isLoggedIn()) { return void cb(null, false); } - var usage; - var andThen = function (e, limit, plan) { - if (e) { return void cb(e); } - var data = {usage: usage, limit: limit, plan: plan}; - if (usage > limit) { - return void cb (null, true, data); - } - return void cb (null, false, data); - }; - var todo = function (e, used) { - if (e) { return void cb(e); } - usage = used; - common.getPinLimit(andThen); - }; - common.getPinnedUsage(todo); - }; - - common.clearOwnedChannel = function (channel, cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - rpc.clearOwnedChannel(channel, cb); - }; - - common.uploadComplete = function (cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - rpc.uploadComplete(cb); - }; - - common.uploadStatus = function (size, cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - rpc.uploadStatus(size, cb); - }; - - common.uploadCancel = function (cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - rpc.uploadCancel(cb); - }; - - // Forget button - // TODO REFACTOR only used in sframe-common-outer - common.moveToTrash = function (cb, href) { - href = href || window.location.href; - common.forgetPad(href, function (err) { - if (err) { - console.log("unable to forget pad"); - console.error(err); - cb(err, null); - return; - } - var n = getNetwork(); - var r = getRealtime(); - if (n && r) { - Realtime.whenRealtimeSyncs(r, function () { - n.disconnect(); - cb(); - }); - } else { - cb(); - } - }); - }; - - // TODO REFACTOR only used in sframe-common-outer - common.saveAsTemplate = function (Cryptput, data, cb) { - var p = Hash.parsePadUrl(window.location.href); - if (!p.type) { return; } - var hash = Hash.createRandomHash(); - var href = '/' + p.type + '/#' + hash; - Cryptput(hash, data.toSave, function (e) { - if (e) { throw new Error(e); } - common.addTemplate(makePad(href, data.title)); - Realtime.whenRealtimeSyncs(getRealtime(), function () { - cb(); - }); - }); + messenger.setChannelHead = function (data, cb) { + postMessage("CONTACTS_SET_CHANNEL_HEAD", data, cb); }; + messenger.onMessageEvent = Util.mkEvent(); + messenger.onJoinEvent = Util.mkEvent(); + messenger.onLeaveEvent = Util.mkEvent(); + messenger.onUpdateEvent = Util.mkEvent(); + messenger.onFriendEvent = Util.mkEvent(); + messenger.onUnfriendEvent = Util.mkEvent(); + // HERE common.getShareHashes = function (secret, cb) { + var hashes; if (!window.location.hash) { - var hashes = Hash.getHashes(secret.channel, secret); + hashes = Hash.getHashes(secret.channel, secret); + return void cb(null, hashes); + } + var parsed = Hash.parsePadUrl(window.location.href); + if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } + if (parsed.type === 'file') { secret.channel = Util.base64ToHex(secret.channel); } + hashes = Hash.getHashes(secret.channel, secret); + + if (!hashes.editHash && !hashes.viewHash && parsed.hashData && !parsed.hashData.mode) { + // It means we're using an old hash + hashes.editHash = window.location.hash.slice(1); return void cb(null, hashes); } - common.getRecentPads(function (err, recent) { - var parsed = Hash.parsePadUrl(window.location.href); - if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } - if (parsed.type === 'file') { secret.channel = Util.base64ToHex(secret.channel); } - var hashes = Hash.getHashes(secret.channel, secret); - - if (!hashes.editHash && !hashes.viewHash && parsed.hashData && !parsed.hashData.mode) { - // It means we're using an old hash - hashes.editHash = window.location.hash.slice(1); - } - - // If we have a stronger version in drive, add it and add a redirect button - var stronger = recent && Hash.findStronger(null, recent); - if (stronger) { - var parsed2 = Hash.parsePadUrl(stronger); - hashes.editHash = parsed2.hash; - } + postMessage("GET_STRONGER_HASH", { + href: window.location.href + }, function (hash) { + if (hash) { hashes.editHash = hash; } cb(null, hashes); }); }; @@ -777,27 +533,84 @@ define([ localStorage[CRYPTPAD_VERSION] = ver; }; + var _onMetadataChanged = []; + common.onMetadataChanged = function (h) { + if (typeof(h) !== "function") { return; } + if (_onMetadataChanged.indexOf(h) !== -1) { return; } + _onMetadataChanged.push(h); + }; + common.changeMetadata = function () { + _onMetadataChanged.forEach(function (h) { h(); }); + }; + + 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 onMessage = function (cmd, data, cb) { + cb = cb || function () {}; + switch (cmd) { + case 'REQUEST_LOGIN': { + requestLogin(); + break; + } + case 'UPDATE_METADATA': { + common.changeMetadata(); + break; + } + case 'UPDATE_TOKEN': { + var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); + if (localToken !== data.token) { requestLogin(); } + break; + } + case 'Q_FRIEND_REQUEST': { + if (!common.onFriendRequest) { break; } + common.onFriendRequest(data, cb); + break; + } + case 'EV_FRIEND_COMPLETE': { + if (!common.onFriendComplete) { break; } + common.onFriendComplete(data); + break; + } + // Messenger + case 'CONTACTS_MESSAGE': { + common.messenger.onMessageEvent.fire(data); break; + } + case 'CONTACTS_JOIN': { + common.messenger.onJoinEvent.fire(data); break; + } + case 'CONTACTS_LEAVE': { + common.messenger.onLeaveEvent.fire(data); break; + } + case 'CONTACTS_UPDATE': { + common.messenger.onUpdateEvent.fire(data); break; + } + case 'CONTACTS_FRIEND': { + common.messenger.onFriendEvent.fire(data); break; + } + case 'CONTACTS_UNFRIEND': { + common.messenger.onUnfriendEvent.fire(data); break; + } + } + }; + common.ready = (function () { var env = {}; var initialized = false; - return function (f) { + return function (f, rdyCfg) { + rdyCfg = rdyCfg || {}; if (initialized) { return void setTimeout(function () { f(void 0, env); }); } - if (sessionStorage[Constants.newPadPathKey]) { - common.initialPath = sessionStorage[Constants.newPadPathKey]; - delete sessionStorage[Constants.newPadPathKey]; - } - - var proxy; - var network; var provideFeedback = function () { - if (Object.keys(proxy).length === 1) { - Feedback.send("FIRST_APP_USE", true); - } - if (typeof(window.Proxy) === 'undefined') { Feedback.send("NO_PROXIES"); } @@ -817,38 +630,48 @@ define([ Feedback.reportScreenDimensions(); Feedback.reportLanguage(); }; - var initFeedback = function () { + var initFeedback = function (feedback) { // Initialize feedback - try { - var entry = Util.find(getProxy(), [ - 'settings', - 'general', - 'allowUserFeedback' - ]); - Feedback.init(entry); - } catch (e) { - console.error(e); - Feedback.init(false); - } + Feedback.init(feedback); provideFeedback(); }; Nthen(function (waitFor) { - Store.ready(waitFor(function (err, storeObj) { - store = common.store = env.store = storeObj; - Messaging.addDirectMessageHandler(common); - proxy = getProxy(); - network = getNetwork(); - network.on('disconnect', function () { - Realtime.setConnectionState(false); - }); - network.on('reconnect', function () { - Realtime.setConnectionState(true); - }); - initFeedback(); - }), common); - }).nThen(function (waitFor) { - $(waitFor()); + var cfg = { + query: onMessage, // TODO temporary, will be replaced by a webworker channel + userHash: LocalStore.getUserHash(), + anonHash: LocalStore.getFSHash(), + localToken: tryParsing(localStorage.getItem(Constants.tokenKey)), + language: common.getLanguage(), + messenger: rdyCfg.messenger + }; + if (sessionStorage[Constants.newPadPathKey]) { + cfg.initialPath = sessionStorage[Constants.newPadPathKey]; + delete sessionStorage[Constants.newPadPathKey]; + } + AStore.query("CONNECT", cfg, waitFor(function (data) { + if (data.error) { throw new Error(data.error); } + + if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); } + + if (cfg.userHash && sessionStorage) { + // copy User_hash into sessionStorage because cross-domain iframes + // on safari replaces localStorage with sessionStorage or something + sessionStorage.setItem(Constants.userHashKey, cfg.userHash); + } + + if (cfg.userHash) { + var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); + if (localToken === null) { + // if that number hasn't been set to localStorage, do so. + localStorage.setItem(Constants.tokenKey, data[Constants.tokenKey]); + } + } + + // TODO ww + //Messaging.addDirectMessageHandler(common); + initFeedback(data.feedback); + })); }).nThen(function (waitFor) { // Load the new pad when the hash has changed var oldHref = document.location.href; @@ -876,114 +699,42 @@ define([ document.location.reload(); } else if (o && !n) { LocalStore.logout(); - if (getNetwork()) { - getNetwork().disconnect(); - } + postMessage("DISCONNECT"); } }); if (PINNING_ENABLED && LocalStore.isLoggedIn()) { console.log("logged in. pads will be pinned"); - var w0 = waitFor(); - Pinpad.create(network, proxy, function (e, call) { - if (e) { - console.error(e); - return w0(); - } - + postMessage("INIT_RPC", null, waitFor(function (obj) { console.log('RPC handshake complete'); - rpc = common.rpc = env.rpc = call; - - common.getPinLimit(function (e, limit, plan, note) { - if (e) { return void console.error(e); } - common.account.limit = limit; - localStorage.plan = common.account.plan = plan; - common.account.note = note; - w0(); - }); - - common.arePinsSynced(function (err, yes) { - if (!yes) { - common.resetPins(function (err) { - if (err) { - console.error("Pin Reset Error"); - return console.error(err); - } - console.log('RESET DONE'); - }); - } - }); - }); + if (obj.error) { return; } + localStorage.plan = obj.plan; + })); } else if (PINNING_ENABLED) { console.log('not logged in. pads will not be pinned'); } else { console.log('pinning disabled'); } - var w1 = waitFor(); - require([ - '/common/rpc.js', - ], function (Rpc) { - Rpc.createAnonymous(network, function (e, call) { - if (e) { - console.error(e); - return void w1(); - } - anon_rpc = common.anon_rpc = env.anon_rpc = call; - w1(); - }); - }); - - // Everything's ready, continue... - if($('#pad-iframe').length) { - var w2 = waitFor(); - var $iframe = $('#pad-iframe'); - var iframe = $iframe[0]; - var iframeDoc = iframe.contentDocument || iframe.contentWindow.document; - if (iframeDoc.readyState === 'complete') { - return void w2(); - } - $iframe.load(w2); //cb); - } + postMessage("INIT_ANON_RPC", null, waitFor(function () { + console.log('Anonymous RPC ready'); + })); }).nThen(function (waitFor) { if (sessionStorage.createReadme) { - var w = waitFor(); - require(['/common/cryptget.js'], function (Crypt) { - var hash = Hash.createRandomHash(); - Crypt.put(hash, Messages.driveReadme, function (e) { - if (e) { - console.error("Error while creating the default pad:", e); - return void w(); - } - var href = '/pad/#' + hash; - var data = { - href: href, - title: Messages.driveReadmeTitle, - atime: +new Date(), - ctime: +new Date() - }; - common.getFO().pushData(data, function (e, id) { - if (e) { - console.error("Error while creating the default pad:", e); - return void w(); - } - common.getFO().add(id); - w(); - }); - }); + var data = { + driveReadme: Messages.driveReadme, + driveReadmeTitle: Messages.driveReadmeTitle, + }; + postMessage("CREATE_README", data, waitFor(function (e) { + if (e && e.error) { return void console.error(e.error); } delete sessionStorage.createReadme; - }); + })); } }).nThen(function (waitFor) { if (sessionStorage.migrateAnonDrive) { - var w = waitFor(); - require(['/common/mergeDrive.js'], function (Merge) { - var hash = LocalStore.getFSHash(); - Merge.anonDriveIntoUser(getStore().getProxy(), hash, function () { - delete sessionStorage.migrateAnonDrive; - w(); - }); - }); + common.mergeAnonDrive(waitFor(function() { + delete sessionStorage.migrateAnonDrive; + })); } }).nThen(function () { updateLocalVersion(); @@ -994,10 +745,5 @@ define([ }()); - // MAGIC that happens implicitly - $(function () { - Language.applyTranslation(); - }); - return common; }); diff --git a/www/common/dom-ready.js b/www/common/dom-ready.js new file mode 100644 index 000000000..69a484493 --- /dev/null +++ b/www/common/dom-ready.js @@ -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(); } + }; + } + }; +}); diff --git a/www/common/flat-dom.js b/www/common/flat-dom.js index 51c3d07b3..25b51d786 100644 --- a/www/common/flat-dom.js +++ b/www/common/flat-dom.js @@ -33,7 +33,7 @@ define([], function () { data.map[id] = el.textContent; return id; } - if (!el || !el.attributes) { return void console.error(el); } + if (!el || !el.attributes) { return; } id = uid(); data.map[id] = [ el.tagName, diff --git a/www/common/fsStore.js b/www/common/fsStore.js deleted file mode 100644 index 6ef2f6d51..000000000 --- a/www/common/fsStore.js +++ /dev/null @@ -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; -}); diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index d61a1db18..ba831ab56 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -2,8 +2,8 @@ define([ '/common/cryptget.js', '/common/userObject.js', '/common/common-hash.js', - '/common/outer/local-store.js', -], function (Crypt, FO, Hash, LocalStore) { + '/common/common-realtime.js', +], function (Crypt, FO, Hash, Realtime) { var exp = {}; var getType = function (el) { @@ -86,7 +86,7 @@ define([ 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 - if (!fsHash || !LocalStore.isLoggedIn()) { + if (!fsHash || !proxyData.loggedIn) { if (typeof(cb) === "function") { return void 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) { var proxy = proxyData.proxy; var oldFo = FO.init(parsed.drive, { - loggedIn: LocalStore.isLoggedIn() + loggedIn: proxyData.loggedIn }); var onMigrated = function () { oldFo.fixFiles(); - var newFo = proxyData.fo; + var newFo = proxyData.userObject; var oldRecentPads = parsed.drive[newFo.FILES_DATA]; var newRecentPads = proxy.drive[newFo.FILES_DATA]; var oldFiles = oldFo.getFiles([newFo.FILES_DATA]); @@ -154,7 +154,9 @@ define([ proxy.FS_hashes = []; } proxy.FS_hashes.push(fsHash); - if (typeof(cb) === "function") { cb(); } + if (typeof(cb) === "function") { + Realtime.whenRealtimeSyncs(proxyData.realtime, cb); + } }; oldFo.migrate(onMigrated); return; diff --git a/www/common/metadata-manager.js b/www/common/metadata-manager.js index 0e375b736..bf99e4b84 100644 --- a/www/common/metadata-manager.js +++ b/www/common/metadata-manager.js @@ -59,7 +59,6 @@ define(['json.sortify'], function (Sortify) { } if (metadataObj.title !== rememberedTitle) { - console.log("Title update\n" + metadataObj.title + '\n'); rememberedTitle = 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) { meta = ev; if (ev.priv) { priv = ev.priv; } + if (netfluxId) { + meta.user.netfluxId = netfluxId; + } + if (!isReady) { + isReady = true; + readyHandlers.forEach(function (f) { f(); }); + } change(true); }); sframeChan.on('EV_RT_CONNECT', function (ev) { - meta.user.netfluxId = ev.myID; + netfluxId = ev.myID; members = ev.members; + if (!meta.user) { return; } + meta.user.netfluxId = netfluxId; change(true); }); sframeChan.on('EV_RT_JOIN', function (ev) { members.push(ev); + if (!meta.user) { return; } change(false); }); sframeChan.on('EV_RT_LEAVE', function (ev) { var idx = members.indexOf(ev); if (idx === -1) { console.log('Error: ' + ev + ' not in members'); return; } members.splice(idx, 1); + if (!meta.user) { return; } change(false); }); sframeChan.on('EV_RT_DISCONNECT', function () { members = []; + if (!meta.user) { return; } change(true); }); @@ -140,6 +154,10 @@ define(['json.sortify'], function (Sortify) { }, getNetfluxId : function () { return meta.user.netfluxId; + }, + onReady: function (f) { + if (isReady) { return void f(); } + readyHandlers.push(f); } }); }; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js new file mode 100644 index 000000000..7415843b1 --- /dev/null +++ b/www/common/outer/async-store.js @@ -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; +}); diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js new file mode 100644 index 000000000..c0b435079 --- /dev/null +++ b/www/common/outer/store-rpc.js @@ -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; +}); + diff --git a/www/common/outer/upload.js b/www/common/outer/upload.js index 4e42d94da..cce3e6b3c 100644 --- a/www/common/outer/upload.js +++ b/www/common/outer/upload.js @@ -20,7 +20,7 @@ define([ var sendChunk = function (box, cb) { var enc = Nacl.util.encodeBase64(box); - common.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) { + common.uploadChunk(enc, function (e, msg) { cb(e, msg); }); }; @@ -58,8 +58,7 @@ define([ if (noStore) { return void onComplete(href); } - common.initialPath = path; - common.renamePad(title || "", href, function (err) { + common.setPadTitle(title || "", href, path, function (err) { if (err) { return void console.error(err); } onComplete(href); common.setPadAttribute('fileType', metadata.type, null, href); diff --git a/www/common/pinpad.js b/www/common/pinpad.js index ebece2626..47eb97892 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -3,13 +3,13 @@ define([ ], function (Rpc) { var create = function (network, proxy, cb) { if (!network) { - window.setTimeout(function () { + setTimeout(function () { cb('INVALID_NETWORK'); }); return; } if (!proxy) { - window.setTimeout(function () { + setTimeout(function () { cb('INVALID_PROXY'); }); return; @@ -19,7 +19,7 @@ define([ var edPublic = proxy.edPublic; if (!(edPrivate && edPublic)) { - window.setTimeout(function () { + setTimeout(function () { cb('INVALID_KEYS'); }); return; @@ -39,7 +39,7 @@ define([ // you can ask the server to pin a particular channel for you exp.pin = function (channels, cb) { if (!Array.isArray(channels)) { - window.setTimeout(function () { + setTimeout(function () { cb('[TypeError] pin expects an array'); }); return; @@ -50,7 +50,7 @@ define([ // you can also ask to unpin a particular channel exp.unpin = function (channels, cb) { if (!Array.isArray(channels)) { - window.setTimeout(function () { + setTimeout(function () { cb('[TypeError] pin expects an array'); }); return; @@ -71,7 +71,7 @@ define([ // if local and remote hashes don't match, send a reset exp.reset = function (channels, cb) { if (!Array.isArray(channels)) { - window.setTimeout(function () { + setTimeout(function () { cb('[TypeError] pin expects an array'); }); return; @@ -163,7 +163,7 @@ define([ exp.uploadStatus = function (size, cb) { if (typeof(size) !== 'number') { - return void window.setTimeout(function () { + return void setTimeout(function () { cb('INVALID_SIZE'); }); } diff --git a/www/common/rpc.js b/www/common/rpc.js index 9f04d7663..e86615111 100644 --- a/www/common/rpc.js +++ b/www/common/rpc.js @@ -140,7 +140,7 @@ types of messages: var send = ctx.send = function (type, msg, cb) { if (!ctx.connected && type !== 'COOKIE') { - return void window.setTimeout(function () { + return void setTimeout(function () { cb('DISCONNECTED'); }); } @@ -185,7 +185,7 @@ types of messages: send.unauthenticated = function (type, msg, cb) { if (!ctx.connected) { - return void window.setTimeout(function () { + return void setTimeout(function () { cb('DISCONNECTED'); }); } @@ -276,7 +276,7 @@ types of messages: var send = ctx.send = function (type, msg, cb) { if (!ctx.connected) { - return void window.setTimeout(function () { + return void setTimeout(function () { cb('DISCONNECTED'); }); } diff --git a/www/common/sframe-app-outer.js b/www/common/sframe-app-outer.js index 3c675a4c3..d0dcb631c 100644 --- a/www/common/sframe-app-outer.js +++ b/www/common/sframe-app-outer.js @@ -2,14 +2,14 @@ define([ '/bower_components/nthen/index.js', '/api/config', - 'jquery', + '/common/dom-ready.js', '/common/requireconfig.js', '/common/sframe-common-outer.js' -], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { +], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { var requireConfig = RequireConfig(); nThen(function (waitFor) { - $(waitFor()); + DomReady.onReady(waitFor()); }).nThen(function (waitFor) { var req = { cfg: requireConfig, @@ -18,7 +18,7 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - $('#sbox-iframe').attr('src', + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); @@ -37,4 +37,4 @@ define([ }).nThen(function (/*waitFor*/) { SFCommonO.start(); }); -}); \ No newline at end of file +}); diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js index a07ce8e0c..4d0370122 100644 --- a/www/common/sframe-common-file.js +++ b/www/common/sframe-common-file.js @@ -212,7 +212,9 @@ define([ 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 extIdx = file.name.lastIndexOf('.'); var name = extIdx !== -1 ? file.name.slice(0,extIdx) : file.name; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 3e6c7c84c..c9c6c9526 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -9,6 +9,7 @@ define([ common.start = function (cfg) { cfg = cfg || {}; var realtime = !cfg.noRealtime; + var network; var secret; var hashes; var CpNfOuter; @@ -18,7 +19,7 @@ define([ var SFrameChannel; var sframeChan; var FilePicker; - var Messenger; + //var Messenger; var Messaging; var Notifier; var Utils = {}; @@ -32,7 +33,6 @@ define([ '/common/cryptget.js', '/common/sframe-channel.js', '/filepicker/main.js', - '/common/common-messenger.js', '/common/common-messaging.js', '/common/common-notifier.js', '/common/common-hash.js', @@ -41,16 +41,17 @@ define([ '/common/common-constants.js', '/common/common-feedback.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, - _FilePicker, _Messenger, _Messaging, _Notifier, _Hash, _Util, _Realtime, - _Constants, _Feedback, _LocalStore) { + _FilePicker, _Messaging, _Notifier, _Hash, _Util, _Realtime, + _Constants, _Feedback, _LocalStore, NetConfig, Netflux) { CpNfOuter = _CpNfOuter; Cryptpad = _Cryptpad; Crypto = _Crypto; Cryptget = _Cryptget; SFrameChannel = _SFrameChannel; FilePicker = _FilePicker; - Messenger = _Messenger; Messaging = _Messaging; Notifier = _Notifier; Utils.Hash = _Hash; @@ -84,7 +85,15 @@ define([ SFrameChannel.create($('#sbox-iframe')[0].contentWindow, waitFor(function (sfc) { sframeChan = sfc; }), 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) { $('#sbox-iframe').focus(); @@ -104,12 +113,23 @@ define([ }); }); - secret = cfg.getSecrets ? cfg.getSecrets(Cryptpad, Utils) : Utils.Hash.getSecrets(); - if (!secret.channel) { - // New pad: create a new random channel id - secret.channel = Utils.Hash.createChannelId(); + if (cfg.getSecrets) { + var w = waitFor(); + cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) { + 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 () { var readOnly = secret.keys && !secret.keys.editKeyStr; @@ -117,59 +137,50 @@ define([ var parsed = Utils.Hash.parsePadUrl(window.location.href); if (!parsed.type) { throw new Error(); } var defaultTitle = Utils.Hash.getDefaultName(parsed); - var proxy = Cryptpad.getProxy(); var updateMeta = function () { //console.log('EV_METADATA_UPDATE'); - var name; + var metaObj, isTemplate; nThen(function (waitFor) { - Cryptpad.getLastName(waitFor(function (err, n) { + Cryptpad.getMetadata(waitFor(function (err, m) { + if (err) { console.log(err); } + metaObj = m; + })); + Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) { if (err) { console.log(err); } - name = n; + isTemplate = t; })); }).nThen(function (/*waitFor*/) { - var metaObj = { - doc: { - defaultTitle: defaultTitle, - type: parsed.type - }, - user: { - name: name, - uid: Cryptpad.getUid(), - avatar: Cryptpad.getAvatarUrl(), - profile: Cryptpad.getProfileUrl(), - curvePublic: proxy.curvePublic, - netfluxId: Cryptpad.getNetwork().webChannels[0].myID, - }, - priv: { - edPublic: proxy.edPublic, - accountName: Utils.LocalStore.getAccountName(), - origin: window.location.origin, - pathname: window.location.pathname, - 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 - } + metaObj.doc = { + defaultTitle: defaultTitle, + type: parsed.type + }; + var additionalPriv = { + accountName: Utils.LocalStore.getAccountName(), + origin: window.location.origin, + pathname: window.location.pathname, + fileHost: ApiConfig.fileHost, + readOnly: readOnly, + availableHashes: hashes, + isTemplate: isTemplate, + feedbackAllowed: Utils.Feedback.state, + isPresent: parsed.hashData && parsed.hashData.present, + isEmbed: parsed.hashData && parsed.hashData.embed, + accounts: { + donateURL: Cryptpad.donateURL, + upgradeURL: Cryptpad.upgradeURL } }; + for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } + if (cfg.addData) { cfg.addData(metaObj.priv, Cryptpad); } + sframeChan.event('EV_METADATA_UPDATE', metaObj); }); }; - Cryptpad.onDisplayNameChanged(updateMeta); + Cryptpad.onMetadataChanged(updateMeta); sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); - proxy.on('change', 'settings', updateMeta); Utils.LocalStore.onLogout(function () { sframeChan.event('EV_LOGOUT'); @@ -223,7 +234,7 @@ define([ sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) { currentTitle = newTitle; setDocumentTitle(); - Cryptpad.renamePad(newTitle, undefined, function (err) { + Cryptpad.setPadTitle(newTitle, undefined, undefined, function (err) { cb(err); }); }); @@ -241,7 +252,7 @@ define([ cb('ERROR'); return; } - Cryptpad.changeDisplayName(newName, true); + Cryptpad.changeMetadata(); cb(); }); }); @@ -287,7 +298,6 @@ define([ }; sframeChan.on('Q_GET_FULL_HISTORY', function (data, cb) { - var network = Cryptpad.getNetwork(); var hkn = network.historyKeeper; var crypto = Crypto.createEncryptor(secret.keys); // Get the history messages and send them to the iframe @@ -445,8 +455,9 @@ define([ Cryptpad.useTemplate(href, Cryptget, cb); }); sframeChan.on('Q_TEMPLATE_EXIST', function (type, cb) { - var hasTemplate = Cryptpad.listTemplates(type).length > 0; - cb(hasTemplate); + Cryptpad.listTemplates(type, function (err, templates) { + cb(templates.length > 0); + }); }); sframeChan.on('EV_GOTO_URL', function (url) { @@ -494,117 +505,58 @@ define([ } if (cfg.messaging) { - var messenger = Messenger.messenger(Cryptpad); - sframeChan.on('Q_CONTACTS_GET_FRIEND_LIST', function (data, cb) { - messenger.getFriendList(function (e, keys) { - cb({ - error: e, - data: keys, - }); - }); + Cryptpad.messenger.getFriendList(cb); }); sframeChan.on('Q_CONTACTS_GET_MY_INFO', function (data, cb) { - messenger.getMyInfo(function (e, info) { - cb({ - error: e, - data: info, - }); - }); + Cryptpad.messenger.getMyInfo(cb); }); sframeChan.on('Q_CONTACTS_GET_FRIEND_INFO', function (curvePublic, cb) { - messenger.getFriendInfo(curvePublic, function (e, info) { - cb({ - error: e, - data: info, - }); - }); + Cryptpad.messenger.getFriendInfo(curvePublic, cb); }); sframeChan.on('Q_CONTACTS_REMOVE_FRIEND', function (curvePublic, cb) { - messenger.removeFriend(curvePublic, function (e, info) { - cb({ - error: e, - data: info, - }); - }); + Cryptpad.messenger.removeFriend(curvePublic, cb); }); sframeChan.on('Q_CONTACTS_OPEN_FRIEND_CHANNEL', function (curvePublic, cb) { - messenger.openFriendChannel(curvePublic, function (e) { - cb({ error: e, }); - }); + Cryptpad.messenger.openFriendChannel(curvePublic, cb); }); sframeChan.on('Q_CONTACTS_GET_STATUS', function (curvePublic, cb) { - messenger.getStatus(curvePublic, function (e, online) { - cb({ - error: e, - data: online, - }); - }); + Cryptpad.messenger.getFriendStatus(curvePublic, cb); }); sframeChan.on('Q_CONTACTS_GET_MORE_HISTORY', function (opt, cb) { - messenger.getMoreHistory(opt.curvePublic, opt.sig, opt.count, function (e, history) { - cb({ - error: e, - data: history, - }); - }); + Cryptpad.messenger.getMoreHistory(opt, cb); }); sframeChan.on('Q_CONTACTS_SEND_MESSAGE', function (opt, cb) { - messenger.sendMessage(opt.curvePublic, opt.content, function (e) { - cb({ - error: e, - }); - }); + Cryptpad.messenger.sendMessage(opt, cb); }); sframeChan.on('Q_CONTACTS_SET_CHANNEL_HEAD', function (opt, cb) { - messenger.setChannelHead(opt.curvePublic, opt.sig, function (e) { - cb({ - error: e - }); - }); + Cryptpad.messenger.setChannelHead(opt, cb); }); sframeChan.on('Q_CONTACTS_CLEAR_OWNED_CHANNEL', function (channel, cb) { - messenger.clearOwnedChannel(channel, function (e) { - cb({ - error: e, - }); - }); + Cryptpad.clearOwnedChannel(channel, cb); }); - messenger.on('message', function (message) { - sframeChan.event('EV_CONTACTS_MESSAGE', message); + Cryptpad.messenger.onMessageEvent.reg(function (data) { + sframeChan.event('EV_CONTACTS_MESSAGE', data); }); - messenger.on('join', function (curvePublic, channel) { - sframeChan.event('EV_CONTACTS_JOIN', { - curvePublic: curvePublic, - channel: channel, - }); + Cryptpad.messenger.onJoinEvent.reg(function (data) { + sframeChan.event('EV_CONTACTS_JOIN', data); }); - messenger.on('leave', function (curvePublic, channel) { - sframeChan.event('EV_CONTACTS_LEAVE', { - curvePublic: curvePublic, - channel: channel, - }); + Cryptpad.messenger.onLeaveEvent.reg(function (data) { + sframeChan.event('EV_CONTACTS_LEAVE', data); }); - messenger.on('update', function (info, curvePublic) { - sframeChan.event('EV_CONTACTS_UPDATE', { - curvePublic: curvePublic, - info: info, - }); + Cryptpad.messenger.onUpdateEvent.reg(function (data) { + sframeChan.event('EV_CONTACTS_UPDATE', data); }); - messenger.on('friend', function (curvePublic) { - sframeChan.event('EV_CONTACTS_FRIEND', { - curvePublic: curvePublic, - }); + Cryptpad.messenger.onFriendEvent.reg(function (data) { + sframeChan.event('EV_CONTACTS_FRIEND', data); }); - messenger.on('unfriend', function (curvePublic) { - sframeChan.event('EV_CONTACTS_UNFRIEND', { - curvePublic: curvePublic, - }); + Cryptpad.messenger.onUnfriendEvent.reg(function (data) { + sframeChan.event('EV_CONTACTS_UNFRIEND', data); }); } @@ -629,7 +581,7 @@ define([ CpNfOuter.start({ sframeChan: sframeChan, channel: secret.channel, - network: cfg.newNetwork || Cryptpad.getNetwork(), + network: cfg.newNetwork || network, validateKey: secret.keys.validateKey || undefined, readOnly: readOnly, crypto: Crypto.createEncryptor(secret.keys), diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index f50ff71da..01ae4c38c 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -328,7 +328,7 @@ define([ nThen(function (waitFor) { SFrameChannel.create(window.parent, waitFor(function (sfc) { ctx.sframeChan = sfc; }), true); // CpNfInner.start() should be here.... - }).nThen(function () { + }).nThen(function (waitFor) { localForage.clear(); 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) { UI.confirm(confirmMsg, cb, null, true); }); @@ -393,6 +389,8 @@ define([ } catch (e) { Feedback.init(false); } }); + ctx.metadataMgr.onReady(waitFor()); + }).nThen(function () { ctx.sframeChan.ready(); cb(funcs); }); diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js index 1227a1dbc..5bdd8486d 100644 --- a/www/common/toolbar3.js +++ b/www/common/toolbar3.js @@ -767,7 +767,7 @@ define([ var origin = privateData.origin; var pathname = privateData.pathname; 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 = $('', { href: href, diff --git a/www/common/userObject.js b/www/common/userObject.js index 58e45e8ee..344677ea8 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -1,12 +1,11 @@ define([ - 'jquery', '/customize/application_config.js', '/common/common-util.js', '/common/common-hash.js', '/common/common-realtime.js', '/common/common-constants.js', '/customize/messages.js' -], function ($, AppConfig, Util, Hash, Realtime, Constants, Messages) { +], function (AppConfig, Util, Hash, Realtime, Constants, Messages) { var module = {}; var ROOT = module.ROOT = "root"; @@ -21,13 +20,13 @@ define([ module.init = function (files, config) { var exp = {}; - var Cryptpad = config.Cryptpad; + var pinPads = config.pinPads; var loggedIn = config.loggedIn; 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 NEW_FOLDER_NAME = Messages.fm_newFolder; - var NEW_FILE_NAME = Messages.fm_newFile; + var NEW_FOLDER_NAME = Messages.fm_newFolder || 'New folder'; + var NEW_FILE_NAME = Messages.fm_newFile || 'New file'; exp.ROOT = ROOT; exp.UNSORTED = UNSORTED; @@ -101,7 +100,7 @@ define([ }; for (var f in element) { if (trashRoot) { - if ($.isArray(element[f])) { + if (Array.isArray(element[f])) { element[f].forEach(addSubfolder); } } else { @@ -119,7 +118,7 @@ define([ }; for (var f in element) { if (trashRoot) { - if ($.isArray(element[f])) { + if (Array.isArray(element[f])) { element[f].forEach(addFile); } } else { @@ -148,14 +147,14 @@ define([ return data.filename || data.title || NEW_FILE_NAME; }; exp.getPadAttribute = function (href, attr, cb) { - cb = cb || $.noop; + cb = cb || function () {}; var id = exp.getIdFromHref(href); if (!id) { return void cb(null, undefined); } var data = getFileData(id); cb(null, clone(data[attr])); }; exp.setPadAttribute = function (href, attr, value, cb) { - cb = cb || $.noop; + cb = cb || function () {}; var id = exp.getIdFromHref(href); if (!id) { return void cb("E_INVAL_HREF"); } if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); } @@ -167,7 +166,7 @@ define([ // PATHS 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; } var result = true; var i = a.length - 1; @@ -265,7 +264,7 @@ define([ } }; for (var e in root) { - if (!$.isArray(root[e])) { + if (!Array.isArray(root[e])) { error("Trash contains a non-array element"); return; } @@ -487,8 +486,6 @@ define([ // FILES DATA exp.pushData = function (data, cb) { - // TODO: can only be called from outside atm - if (!Cryptpad) { return; } if (typeof cb !== "function") { cb = function () {}; } var todo = function () { var id = Util.createRandomInteger(); @@ -498,8 +495,9 @@ define([ if (!loggedIn || !AppConfig.enablePinning || config.testMode) { return void todo(); } - Cryptpad.pinPads([Hash.hrefToHexChannelId(data.href)], function (e) { - if (e) { return void cb(e); } + if (!pinPads) { return; } + pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) { + if (obj && obj.error) { return void cb(obj.error); } todo(); }); }; @@ -968,7 +966,7 @@ define([ var addToClean = function (obj, idx, el) { if (typeof(obj) !== "object") { 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") { // We have an old file (href) which is not in filesData: add it var id = Util.createRandomInteger(); diff --git a/www/common/wire.js b/www/common/wire.js new file mode 100644 index 000000000..c4b59cfae --- /dev/null +++ b/www/common/wire.js @@ -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; +}); diff --git a/www/contacts/main.js b/www/contacts/main.js index f99e7d472..38d6c5e71 100644 --- a/www/contacts/main.js +++ b/www/contacts/main.js @@ -2,15 +2,15 @@ define([ '/bower_components/nthen/index.js', '/api/config', - 'jquery', + '/common/dom-ready.js', '/common/requireconfig.js', '/common/sframe-common-outer.js' -], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { +], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { var requireConfig = RequireConfig(); // Loaded in load #2 nThen(function (waitFor) { - $(waitFor()); + DomReady.onReady(waitFor()); }).nThen(function (waitFor) { var req = { cfg: requireConfig, @@ -19,7 +19,7 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - $('#sbox-iframe').attr('src', + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/contacts/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); diff --git a/www/debug/inner.js b/www/debug/inner.js index f45670b41..bb4e1ecf2 100644 --- a/www/debug/inner.js +++ b/www/debug/inner.js @@ -1,10 +1,8 @@ define([ 'jquery', '/bower_components/chainpad-crypto/crypto.js', - '/bower_components/textpatcher/TextPatcher.js', '/common/toolbar3.js', 'json.sortify', - '/bower_components/chainpad-json-validator/json-ot.js', '/common/common-util.js', '/bower_components/nthen/index.js', '/common/sframe-common.js', @@ -22,10 +20,8 @@ define([ ], function ( $, Crypto, - TextPatcher, Toolbar, JSONSortify, - JsonOT, Util, nThen, SFCommon, @@ -61,7 +57,6 @@ define([ var config = APP.config = { readOnly: readOnly, - transformFunction: JsonOT.validate, // cryptpad debug logging (default is 1) // logLevel: 0, validateContent: function (content) { @@ -123,11 +118,7 @@ define([ config.onReady = function (info) { if (APP.realtime !== info.realtime) { - var realtime = APP.realtime = info.realtime; - APP.patchText = TextPatcher.create({ - realtime: realtime, - //logging: true - }); + APP.realtime = info.realtime; } var userDoc = APP.realtime.getUserDoc(); diff --git a/www/drive/main.js b/www/drive/main.js index 1e3ff3589..de668fc4d 100644 --- a/www/drive/main.js +++ b/www/drive/main.js @@ -2,17 +2,15 @@ define([ '/bower_components/nthen/index.js', '/api/config', - 'jquery', + '/common/dom-ready.js', '/common/requireconfig.js', '/common/sframe-common-outer.js', - '/common/outer/network-config.js', - '/bower_components/netflux-websocket/netflux-client.js', -], function (nThen, ApiConfig, $, RequireConfig, SFCommonO, NetConfig, Netflux) { +], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { var requireConfig = RequireConfig(); // Loaded in load #2 nThen(function (waitFor) { - $(waitFor()); + DomReady.onReady(waitFor()); }).nThen(function (waitFor) { var req = { cfg: requireConfig, @@ -21,7 +19,7 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - $('#sbox-iframe').attr('src', + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/drive/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); @@ -38,10 +36,10 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - var getSecrets = function (Cryptpad, Utils) { + var getSecrets = function (Cryptpad, Utils, cb) { var hash = window.location.hash.slice(1) || Utils.LocalStore.getUserHash() || Utils.LocalStore.getFSHash(); - return Utils.Hash.getSecrets('drive', hash); + cb(null, Utils.Hash.getSecrets('drive', hash)); }; var addRpc = function (sframeChan, Cryptpad, Utils) { sframeChan.on('EV_BURN_ANON_DRIVE', function () { @@ -51,13 +49,13 @@ define([ window.location.reload(); }); }; - Netflux.connect(NetConfig.getWebsocketURL()).then(function (network) { + //Netflux.connect(NetConfig.getWebsocketURL()).then(function (network) { SFCommonO.start({ getSecrets: getSecrets, - newNetwork: network, + //newNetwork: network, noHash: true, addRpc: addRpc }); - }, function (err) { console.error(err); }); + //}, function (err) { console.error(err); }); }); }); diff --git a/www/drive/tests.js b/www/drive/tests.js index 23ed2172f..7132fd8a7 100644 --- a/www/drive/tests.js +++ b/www/drive/tests.js @@ -70,7 +70,7 @@ define([ module.test = function (assert) { var config = { - Cryptpad: Cryptpad, + pinPads: Cryptpad.pinPads, workgroup: false, testMode: true, loggedIn: false @@ -325,7 +325,12 @@ define([ var fo = FO.init(files, config); fo.fixFiles(); - var data = Cryptpad.makePad(href5, 'Title5'); + var data = { + href: href5, + title: 'Title5', + atime: +new Date(), + ctime: +new Date() + }; var res; var id5; // pushData is synchronous in test mode (no pinning) diff --git a/www/file/inner.js b/www/file/inner.js index 09a302b0a..0bc7a2000 100644 --- a/www/file/inner.js +++ b/www/file/inner.js @@ -120,7 +120,6 @@ define([ decrypted.callback(); } - console.log(decrypted); $dlview.show(); $dlform.hide(); var $dlButton = $dlview.find('media-tag button'); @@ -174,7 +173,6 @@ define([ var progress = e.originalEvent; var p = progress.percent +'%'; $progress.width(p); - console.log(progress.percent); }); /** diff --git a/www/file/main.js b/www/file/main.js index 0af6558ef..e59957299 100644 --- a/www/file/main.js +++ b/www/file/main.js @@ -2,15 +2,15 @@ define([ '/bower_components/nthen/index.js', '/api/config', - 'jquery', + '/common/dom-ready.js', '/common/requireconfig.js', '/common/sframe-common-outer.js' -], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { +], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { var requireConfig = RequireConfig(); // Loaded in load #2 nThen(function (waitFor) { - $(waitFor()); + DomReady.onReady(waitFor()); }).nThen(function (waitFor) { var req = { cfg: requireConfig, @@ -19,7 +19,7 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - $('#sbox-iframe').attr('src', + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/file/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); diff --git a/www/filepicker/main.js b/www/filepicker/main.js index d628b401c..8b7ab2b60 100644 --- a/www/filepicker/main.js +++ b/www/filepicker/main.js @@ -46,41 +46,30 @@ define([ sframeChan = sfc; })); }).nThen(function () { - var proxy = Cryptpad.getProxy(); var updateMeta = function () { //console.log('EV_METADATA_UPDATE'); - var name; + var metaObj; nThen(function (waitFor) { - Cryptpad.getLastName(waitFor(function (err, n) { + Cryptpad.getMetadata(waitFor(function (err, n) { if (err) { console.log(err); } - name = n; + metaObj = n; })); }).nThen(function (/*waitFor*/) { - sframeChan.event('EV_METADATA_UPDATE', { - doc: {}, - user: { - name: name, - uid: Cryptpad.getUid(), - avatar: Cryptpad.getAvatarUrl(), - profile: Cryptpad.getProfileUrl(), - curvePublic: proxy.curvePublic, - netfluxId: Cryptpad.getNetwork().webChannels[0].myID, - }, - priv: { - 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 - } - }); + metaObj.doc = {}; + var additionalPriv = { + accountName: Utils.LocalStore.getAccountName(), + origin: window.location.origin, + pathname: window.location.pathname, + feedbackAllowed: Utils.Feedback.state, + types: config.types + }; + for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } + + sframeChan.event('EV_METADATA_UPDATE', metaObj); }); }; - Cryptpad.onDisplayNameChanged(updateMeta); + Cryptpad.onMetadataChanged(updateMeta); sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); - proxy.on('change', 'settings', updateMeta); config.addCommonRpc(sframeChan); diff --git a/www/poll/main.js b/www/poll/main.js index 04c9b8f2f..737038ead 100644 --- a/www/poll/main.js +++ b/www/poll/main.js @@ -2,15 +2,15 @@ define([ '/bower_components/nthen/index.js', '/api/config', - 'jquery', + '/common/dom-ready.js', '/common/requireconfig.js', '/common/sframe-common-outer.js', -], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { +], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { var requireConfig = RequireConfig(); // Loaded in load #2 nThen(function (waitFor) { - $(waitFor()); + DomReady.onReady(waitFor()); }).nThen(function (waitFor) { var req = { cfg: requireConfig, @@ -19,7 +19,7 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - $('#sbox-iframe').attr('src', + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/poll/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); diff --git a/www/profile/app-profile.less b/www/profile/app-profile.less index f7cd747d3..21e659a03 100644 --- a/www/profile/app-profile.less +++ b/www/profile/app-profile.less @@ -73,6 +73,9 @@ margin: 5px; } } + .cp-app-profile-resizer { + text-align: center; + } #cp-app-profile-displayname, #cp-app-profile-link { width: 100%; height: 40px; diff --git a/www/profile/inner.js b/www/profile/inner.js index 9b48e2ca2..6cab88eae 100644 --- a/www/profile/inner.js +++ b/www/profile/inner.js @@ -20,6 +20,8 @@ define([ 'css!/bower_components/bootstrap/dist/css/bootstrap.min.css', 'less!/bower_components/components-font-awesome/css/font-awesome.min.css', 'less!/customize/src/less2/main.less', + '/bower_components/croppie/croppie.min.js', + 'css!/bower_components/croppie/croppie.css', ], function ( $, Crypto, @@ -253,12 +255,44 @@ define([ createEditableInput($block, LINK_ID, placeholder, getValue, setValue); }; + var AVATAR_SIZE_LIMIT = 0.5; var allowedMediaTypes = [ 'image/png', 'image/jpeg', 'image/jpg', 'image/gif', ]; + var transformAvatar = function (file, cb) { + if (file.type === 'image/gif') { return void cb(file); } + var $croppie = $('
', { + '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 $block = $('
', {id: AVATAR_ID}).appendTo($container); var $span = $('').appendTo($block); @@ -318,14 +352,30 @@ define([ } }; APP.FM = common.createFileManager(fmConfig); + var accepted = ".gif,.jpg,.jpeg,.png"; var data = { FM: APP.FM, filter: function (file) { var sizeMB = Util.bytesToMegabytes(file.size); 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); $upButton.text(Messages.profile_upload); @@ -395,15 +445,6 @@ define([ var onReady = function () { 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) { var $header = $('
', {id: HEADER_ID}).appendTo(APP.$rightside); addAvatar($header); diff --git a/www/profile/main.js b/www/profile/main.js index 195dbdc6b..22823d0d9 100644 --- a/www/profile/main.js +++ b/www/profile/main.js @@ -2,15 +2,15 @@ define([ '/bower_components/nthen/index.js', '/api/config', - 'jquery', + '/common/dom-ready.js', '/common/requireconfig.js', '/common/sframe-common-outer.js', -], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { +], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { var requireConfig = RequireConfig(); // Loaded in load #2 nThen(function (waitFor) { - $(waitFor()); + DomReady.onReady(waitFor()); }).nThen(function (waitFor) { var req = { cfg: requireConfig, @@ -19,7 +19,7 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - $('#sbox-iframe').attr('src', + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/profile/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); @@ -36,34 +36,41 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - var getSecrets = function (Cryptpad, Utils) { + var getSecrets = function (Cryptpad, Utils, cb) { var Hash = Utils.Hash; // 1st case: visiting someone else's profile with hash in the URL 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 obj = Cryptpad.getProxy(); - if (obj.profile && obj.profile.view && obj.profile.edit) { - return Hash.getSecrets('profile', obj.profile.edit); - } - // 3rd case: profile creation (create a new random hash, store it later if needed) - if (!Utils.LocalStore.isLoggedIn()) { return; } - 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 editHash; + nThen(function (waitFor) { + // 2nd case: visiting our own existing profile + Cryptpad.getProfileEditUrl(waitFor(function (hash) { + editHash = hash; + })); + }).nThen(function () { + if (editHash) { + return void cb(null, Hash.getSecrets('profile', editHash)); } - obj.profile = {}; - obj.profile.edit = Utils.Hash.getEditHashFromKeys(secret.channel, secret.keys); - obj.profile.view = Utils.Hash.getViewHashFromKeys(secret.channel, secret.keys); + // 3rd case: profile creation (create a new random hash, store it later if needed) + if (!Utils.LocalStore.isLoggedIn()) { return void cb(); } + 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) { // 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); Cryptpad.pinPads([chanId], function (e) { if (e) { return void cb(e); } - Cryptpad.getProxy().profile.avatar = data; - Utils.Realtime.whenRealtimeSyncs(Cryptpad.getRealtime(), function () { - cb(); - }); + Cryptpad.setAvatar(data, cb); }); }); // Removing the avatar from the profile: unpin it sframeChan.on('Q_PROFILE_AVATAR_REMOVE', function (data, cb) { var chanId = Utils.Hash.hrefToHexChannelId(data); - Cryptpad.unpinPads([chanId], function (e) { - delete Cryptpad.getProxy().profile.avatar; - cb(e); + Cryptpad.unpinPads([chanId], function () { + Cryptpad.setAvatar(undefined, cb); }); }); }; diff --git a/www/settings/main.js b/www/settings/main.js index 958dcda45..42f501075 100644 --- a/www/settings/main.js +++ b/www/settings/main.js @@ -2,15 +2,15 @@ define([ '/bower_components/nthen/index.js', '/api/config', - 'jquery', + '/common/dom-ready.js', '/common/requireconfig.js', '/common/sframe-common-outer.js' -], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { +], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { var requireConfig = RequireConfig(); // Loaded in load #2 nThen(function (waitFor) { - $(waitFor()); + DomReady.onReady(waitFor()); }).nThen(function (waitFor) { var req = { cfg: requireConfig, @@ -19,7 +19,7 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - $('#sbox-iframe').attr('src', + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/settings/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); @@ -43,7 +43,7 @@ define([ }); }); sframeChan.on('Q_SETTINGS_DRIVE_GET', function (d, cb) { - cb(Cryptpad.getProxy()); + Cryptpad.getUserObject(cb); }); sframeChan.on('Q_SETTINGS_DRIVE_SET', function (data, cb) { var sjson = JSON.stringify(data); @@ -57,26 +57,13 @@ define([ }); }); sframeChan.on('Q_SETTINGS_DRIVE_RESET', function (data, cb) { - var proxy = Cryptpad.getProxy(); - var realtime = Cryptpad.getRealtime(); - proxy.drive = Cryptpad.getStore().getEmptyObject(); - Utils.Realtime.whenRealtimeSyncs(realtime, cb); + Cryptpad.resetDrive(cb); }); sframeChan.on('Q_SETTINGS_LOGOUT', function (data, cb) { - var proxy = Cryptpad.getProxy(); - 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); + Cryptpad.logoutFromAll(cb); }); sframeChan.on('Q_SETTINGS_IMPORT_LOCAL', function (data, cb) { - var proxyData = Cryptpad.getStore().getProxy(); - require([ - '/common/mergeDrive.js', - ], function (Merge) { - Merge.anonDriveIntoUser(proxyData, Utils.LocalStore.getFSHash(), cb); - }); + Cryptpad.mergeAnonDrive(cb); }); }; SFCommonO.start({ diff --git a/www/todo/main.js b/www/todo/main.js index 3f6d1cd5f..2898434fd 100644 --- a/www/todo/main.js +++ b/www/todo/main.js @@ -2,15 +2,15 @@ define([ '/bower_components/nthen/index.js', '/api/config', - 'jquery', + '/common/dom-ready.js', '/common/requireconfig.js', '/common/sframe-common-outer.js' -], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { +], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { var requireConfig = RequireConfig(); // Loaded in load #2 nThen(function (waitFor) { - $(waitFor()); + DomReady.onReady(waitFor()); }).nThen(function (waitFor) { var req = { cfg: requireConfig, @@ -19,7 +19,7 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - $('#sbox-iframe').attr('src', + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/todo/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req))); @@ -36,12 +36,12 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - var getSecrets = function (Cryptpad, Utils) { - var proxy = Cryptpad.getProxy(); - var hash = proxy.todo || Utils.Hash.createRandomHash(); - if (!proxy.todo) { proxy.todo = hash; } - - return Utils.Hash.getSecrets('todo', hash); + var getSecrets = function (Cryptpad, Utils, cb) { + Cryptpad.getTodoHash(function (hash) { + var nHash = hash || Utils.Hash.createRandomHash(); + if (!hash) { Cryptpad.setTodoHash(nHash); } + cb(null, Utils.Hash.getSecrets('todo', hash)); + }); }; SFCommonO.start({ getSecrets: getSecrets, diff --git a/www/whiteboard/main.js b/www/whiteboard/main.js index 4c8a3887c..ce1f14d9c 100644 --- a/www/whiteboard/main.js +++ b/www/whiteboard/main.js @@ -2,15 +2,15 @@ define([ '/bower_components/nthen/index.js', '/api/config', - 'jquery', + '/common/dom-ready.js', '/common/requireconfig.js', '/common/sframe-common-outer.js' -], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) { +], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) { var requireConfig = RequireConfig(); // Loaded in load #2 nThen(function (waitFor) { - $(waitFor()); + DomReady.onReady(waitFor()); }).nThen(function (waitFor) { var req = { cfg: requireConfig, @@ -19,7 +19,7 @@ define([ }; window.rc = requireConfig; window.apiconf = ApiConfig; - $('#sbox-iframe').attr('src', + document.getElementById('sbox-iframe').setAttribute('src', ApiConfig.httpSafeOrigin + '/whiteboard/inner.html?' + requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req)));