diff --git a/lib/hk-util.js b/lib/hk-util.js index 0c7985742..0bb96a52b 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -407,19 +407,9 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => { // check if the "hash" the client is requesting exists in the index const lkh = index.offsetByHash[lastKnownHash]; - // we evict old hashes from the index as new checkpoints are discovered. - // if someone connects and asks for a hash that is no longer relevant, - // we tell them it's an invalid request. This is because of the semantics of "GET_HISTORY" - // which is only ever used when connecting or reconnecting in typical uses of history... - // this assumption should hold for uses by chainpad, but perhaps not for other uses cases. - // EXCEPT: other cases don't use checkpoints! - // clients that are told that their request is invalid should just make another request - // without specifying the hash, and just trust the server to give them the relevant data. - // QUESTION: does this mean mailboxes are causing the server to store too much stuff in memory? - if (lastKnownHash && typeof(lkh) !== "number") { - waitFor.abort(); - return void cb(new Error('EUNKNOWN')); - } + + // fall through to the next block if the offset of the hash in question is not in memory + if (lastKnownHash && typeof(lkh) !== "number") { return; } // Since last 2 checkpoints if (!lastKnownHash) { @@ -441,13 +431,13 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => { offset = lkh; })); }).nThen((w) => { - // if offset is less than zero then presumably the channel has no messages - // returning falls through to the next block and therefore returns -1 + // skip past this block if the offset is anything other than -1 + // this basically makes these first two nThen blocks behave like if-else if (offset !== -1) { return; } - // do a lookup from the index - // FIXME maybe we don't need this anymore? - // otherwise we have a non-negative offset and we can start to read from there + // either the message exists in history but is not in the cached index + // or it does not exist at all. In either case 'getHashOffset' is expected + // to return a number: -1 if not present, positive interger otherwise Env.getHashOffset(channelName, lastKnownHash, w(function (err, _offset) { if (err) { w.abort(); @@ -482,7 +472,9 @@ const getHistoryAsync = (Env, channelName, lastKnownHash, beforeHash, handler, c offset = os; })); }).nThen((waitFor) => { - if (offset === -1) { return void cb(new Error("could not find offset")); } + if (offset === -1) { + return void cb(new Error('EUNKNOWN')); + } const start = (beforeHash) ? 0 : offset; store.readMessagesBin(channelName, start, (msgObj, readMore, abort) => { if (beforeHash && msgObj.offset >= offset) { return void abort(); } diff --git a/lib/storage/file.js b/lib/storage/file.js index 994680218..255f74016 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -1044,7 +1044,7 @@ module.exports.create = function (conf, _cb) { getWeakLock: function (channelName, _cb) { var cb = Util.once(Util.mkAsync(_cb)); - if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } + //if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); } // XXX schedule.unordered(channelName, cb); }, diff --git a/lib/workers/db-worker.js b/lib/workers/db-worker.js index 65a381479..0b9de4f53 100644 --- a/lib/workers/db-worker.js +++ b/lib/workers/db-worker.js @@ -153,7 +153,8 @@ const computeIndex = function (data, cb) { messageBuf = []; } } else if (messageBuf.length > 100 && cpIndex.length === 0) { - messageBuf = messageBuf.slice(0, 50); + // take the last 50 messages + messageBuf = messageBuf.slice(-50); } // if it's not metadata or a checkpoint then it should be a regular message // store it in the buffer @@ -352,7 +353,8 @@ const getMultipleFileSize = function (data, cb) { const getHashOffset = function (data, cb) { const channelName = data.channel; - const lastKnownHash = data.lastKnownHash; + const lastKnownHash = data.hash; + if (typeof(lastKnownHash) !== 'string') { return void cb("INVALID_HASH"); } var offset = -1; store.readMessagesBin(channelName, 0, (msgObj, readMore, abort) => { diff --git a/scripts/tests/test-mailbox.js b/scripts/tests/test-mailbox.js index b0418c5db..d152cf0f2 100644 --- a/scripts/tests/test-mailbox.js +++ b/scripts/tests/test-mailbox.js @@ -11,8 +11,14 @@ var Hash = require("../../www/common/common-hash"); var CpNetflux = require("../../www/bower_components/chainpad-netflux"); var Util = require("../../lib/common-util"); -var createMailbox = function (config, cb) { +// you need more than 100 messages in the history, and you need a lastKnownHash between "50" and "length - 50" + +var createMailbox = function (config, _cb) { + var cb = Util.once(Util.mkAsync(_cb)); + var webchannel; + var user = config.user; + user.messages = []; CpNetflux.start({ network: config.network, @@ -21,11 +27,16 @@ var createMailbox = function (config, cb) { owners: [ config.edPublic ], noChainPad: true, + + lastKnownHash: config.lastKnownHash, + onChannelError: function (err) { + cb(err); + }, onConnect: function (wc /*, sendMessage */) { webchannel = wc; }, - onMessage: function (/* msg, user, vKey, isCp, hash, author */) { - + onMessage: function (msg /*, user, vKey, isCp, hash, author */) { + user.messages.push(msg); }, onReady: function () { cb(void 0, webchannel); @@ -37,6 +48,8 @@ process.on('unhandledRejection', function (err) { console.error(err); }); +var state = {}; + var makeCurveKeys = function () { var pair = Nacl.box.keyPair(); return { @@ -53,6 +66,10 @@ var makeEdKeys = function () { }; }; +var edKeys = makeEdKeys(); +var curveKeys = makeCurveKeys(); +var mailboxChannel = Hash.createChannelId(); + var createUser = function (config, cb) { // config should contain keys for a team rpc (ed) // teamEdKeys @@ -75,11 +92,11 @@ var createUser = function (config, cb) { // make all the parameters you'll need var network = user.network = user.config.network; - user.edKeys = makeEdKeys(); + user.edKeys = edKeys; + user.curveKeys = curveKeys; - user.curveKeys = makeCurveKeys(); user.mailbox = Mailbox.createEncryptor(user.curveKeys); - user.mailboxChannel = Hash.createChannelId(); + user.mailboxChannel = mailboxChannel; // create an anon rpc for alice Rpc.createAnonymous(network, w(function (err, rpc) { @@ -109,6 +126,11 @@ var createUser = function (config, cb) { }).nThen(function (w) { // create and subscribe to your mailbox createMailbox({ + user: user, + + + lastKnownHash: config.lastKnownHash, + network: user.network, channel: user.mailboxChannel, crypto: user.mailbox, @@ -116,8 +138,9 @@ var createUser = function (config, cb) { }, w(function (err /*, wc*/) { if (err) { w.abort(); - console.error("Mailbox creation error"); - process.exit(1); + //console.error("Mailbox creation error"); + cb(err); + //process.exit(1); } //wc.leave(); })); @@ -135,14 +158,10 @@ var createUser = function (config, cb) { var alice; -var sharedConfig = { - teamEdKeys: makeEdKeys(), - teamCurveKeys: makeCurveKeys(), - rosterSeed: Crypto.Team.createSeed(), -}; - nThen(function (w) { - createUser(sharedConfig, w(function (err, _alice) { + createUser({ + //sharedConfig + }, w(function (err, _alice) { if (err) { w.abort(); return void console.log(err); @@ -163,13 +182,18 @@ nThen(function (w) { var i = 0; var next = w(); + state.hashes = []; + var send = function () { - if (i++ >= 300) { return next(); } + if (i++ >= 160) { return next(); } var msg = alice.mailbox.encrypt(JSON.stringify({ pewpew: 'bangbang', }), alice.curveKeys.curvePublic); + var hash = msg.slice(0, 64); + state.hashes.push(hash); + alice.anonRpc.send('WRITE_PRIVATE_MESSAGE', [ alice.mailboxChannel, msg @@ -177,10 +201,33 @@ nThen(function (w) { ], w(function (err) { if (err) { throw new Error(err); } console.log('message %s written successfully', i); - setTimeout(send, 250); + setTimeout(send, 15); })); }; send(); +}).nThen(function (w) { + console.log("Connecting with second user"); + createUser({ + lastKnownHash: state.hashes[55], + }, w(function (err, _alice) { + if (err) { + w.abort(); + console.log("lastKnownHash: ", state.hashes[55]); + console.log(err); + process.exit(1); + //return void console.log(err); + } + var user = state.alice2 = _alice; + + if (user.messages.length === 105) { + process.exit(0); + } + //console.log(user.messages, user.messages.length); + process.exit(1); + })); +}).nThen(function () { + + }).nThen(function () { alice.cleanup(); //bob.cleanup(); diff --git a/server.js b/server.js index 9b82d7c3c..2e6869b9f 100644 --- a/server.js +++ b/server.js @@ -43,6 +43,7 @@ if (process.env.PACKAGE) { } config.httpUnsafeOrigin = config.httpUnsafeOrigin.trim(); + config.httpSafeOrigin = config.httpSafeOrigin.trim().replace(/\/$/, ''); // fall back to listening on a local address // if httpAddress is not a string @@ -129,7 +130,7 @@ var setHeaders = (function () { /^\/common\/onlyoffice\/.*\/index\.html.*/, /^\/(sheet|ooslide|oodoc)\/inner\.html.*/, ].some((regex) => { - return regex.test(req.url) + return regex.test(req.url); }) ? padHeaders : headers; for (let header in h) { res.setHeader(header, h[header]); } }; diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index c385a606c..5a8a594a1 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -288,6 +288,23 @@ define([ return patch; }; + var removeMermaidClickables = function ($el) { + // find all links in the tree and do the following for each one + $el.find('a').each(function (index, a) { + var parent = a.parentElement; + if (!parent) { return; } + // iterate over the links' children and transform them into preceding children + // to preserve their visible ordering + slice(a.children).forEach(function (child) { + parent.insertBefore(child, a); + }); + // remove the link once it has been emptied + $(a).remove(); + }); + // finally, find all 'clickable' items and remove the class + $el.find('.clickable').removeClass('clickable'); + }; + DiffMd.apply = function (newHtml, $content, common) { var contextMenu = common.importMediaTagMenu(); var id = $content.attr('id'); @@ -413,7 +430,7 @@ define([ var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'childList') { - var list_values = [].slice.call(mutation.target.children) + var list_values = slice(mutation.target.children) .map(function (el) { return el.outerHTML; }) .join(''); mediaMap[mutation.target.getAttribute('src')] = list_values; @@ -469,7 +486,13 @@ define([ // check if you had cached a pre-rendered instance of the supplied source if (typeof(cached) !== 'object') { try { - Mermaid.init(undefined, $(el)); + var $el = $(el); + Mermaid.init(undefined, $el); + // clickable elements in mermaid don't work well with our sandboxing setup + // the function below strips clickable elements but still leaves behind some artifacts + // tippy tooltips might still be useful, so they're not removed. It would be + // preferable to just support links, but this covers up a rough edge in the meantime + removeMermaidClickables($el); } catch (e) { console.error(e); } return; } diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index 7024bcfe3..b6f4c6aaf 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -112,11 +112,16 @@ define([ }); sframeChan.on('EV_RT_CONNECT', function (content) { //content.members.forEach(userList.onJoin); + if (isReady && myID === content.myID) { + // We are connected and we are "reconnecting" ==> we probably had to rejoin + // the channel because of a server error (enoent), don't update the toolbar + return; + } isReady = false; if (myID) { // it's a reconnect myID = content.myID; - chainpad.start(); + //chainpad.start(); onConnectionChange({ state: true, myId: myID }); return; } diff --git a/www/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js index 9e3b957fc..84b90f24e 100644 --- a/www/common/sframe-chainpad-netflux-outer.js +++ b/www/common/sframe-chainpad-netflux-outer.js @@ -45,7 +45,8 @@ define([], function () { return decryptedMsg; } catch (err) { console.error(err); - return msg; + console.warn(peer, msg); + return false; } }; @@ -81,6 +82,7 @@ define([], function () { validateKey = msgObj.validateKey; } var message = msgIn(msgObj.user, msgObj.msg); + if (!message) { return; } verbose(message); diff --git a/www/common/translations/messages.fr.json b/www/common/translations/messages.fr.json index c5c5b4649..17582944a 100644 --- a/www/common/translations/messages.fr.json +++ b/www/common/translations/messages.fr.json @@ -945,7 +945,7 @@ "creation_expiration": "Date d'expiration", "creation_passwordValue": "Mot de passe", "creation_propertiesTitle": "Disponibilité", - "creation_appMenuName": "Mode avancé (Ctrl + E)", + "creation_appMenuName": "Nouveau pad (Ctrl + E)", "creation_newPadModalDescription": "Cliquez sur un type de pad pour le créer. Vous pouvez aussi appuyer sur Tab pour sélectionner un type et appuyer sur Entrée pour valider.", "creation_newPadModalDescriptionAdvanced": "Cochez la case si vous souhaitez voir l'écran de création de pads (pour les pads avec propriétaire ou à durée de vie). Vous pouvez appuyer sur Espace pour changer sa valeur.", "creation_newPadModalAdvanced": "Afficher l'écran de création de pads", diff --git a/www/common/translations/messages.it.json b/www/common/translations/messages.it.json index 0bd465dbd..737d39f11 100644 --- a/www/common/translations/messages.it.json +++ b/www/common/translations/messages.it.json @@ -1306,7 +1306,7 @@ "team_cat_general": "Info", "properties_passwordWarning": "La password è stata cambiata ma non siamo in grado di aggiornare il tuo CryptDrive con i nuovi dati. Devi rimuovere i vecchi pad manualmente.
Premi OK per ricaricare ed aggiornare i tuoi diritti di accesso.", "share_embedCategory": "Incorporamento", - "chrome68": "Sembra che tu stia usando il browser Chrome o Chromium versione 68. Contiene un bug che si manifesta nella pagina che diventa completamente bianca dopo alcuni secondi o smette di rispondere ai clic. Per risolvere il problema puoi passare ad un'altra tab e tornare a questa, o provare a scorrere la pagina. Questo problema sarà risolto nella prossima versione del tuo browser.", + "chrome68": "Sembra che tu stia usando il browser Chrome o Chromium versione 68. Contiene un bug che si manifesta nella pagina che diventa completamente bianca dopo alcuni secondi o smette di rispondere ai clic. Per risolvere il problema puoi passare ad un'altra scheda e tornare a questa, o provare a scorrere la pagina. Questo problema sarà risolto nella prossima versione del tuo browser.", "crowdfunding_popup_text": "

Abbiamo bisogno del tuo aiuto!

Per garantire che CryptPad sia attivamente sviluppato, valuta se aiutare il progetto sulla pagina OpenCollective, dove potrai vedere la nostra Roadmap e gli obiettivi di finanziamento.", "admin_updateLimitHint": "Forzare un aggiornamento dei limiti dello spazio utente può essere fatto in qualsiasi momento, ma è necessario solo in caso di errori", "admin_flushCacheTitle": "Svuota la cache HTTP", @@ -1316,7 +1316,7 @@ "fm_info_sharedFolderHistory": "questa è solo la cronologia delle tue cartelle condivise: {0}
Il tuo CryptDrive rimarrà in sola lettura durante la navigazione.", "share_description": "Scegli cosa vuoi condividere e prendi il link o invialo direttamente ai tuoi contatti CryptPad.", "admin_supportInitHelp": "Il tuo server non è ancora configurato per avere una mailbox di supporto. Se vuoi una mailbox di supporto per ricevere messaggi dai tuoi utenti, chiedi all'amministratore del server di avviare lo script posizionato in \"./scripts/generate-admin-keys.js\", quindi salvare la chiave pubblica nel file \"config.js\" ed inviarti la chiave privata.", - "admin_supportInitPrivate": "La tua installazione CryptPad è configurata per usare una mailbox di supporto ma il tuo account non ha la chiave privata corretta per accedervi. Usa la form che segue per aggiungere o aggiornare la chiave privata del tuo account.", + "admin_supportInitPrivate": "La tua installazione CryptPad è configurata per usare una mailbox di supporto ma il tuo account non ha la chiave privata corretta per accedervi. Usa il modulo che segue per aggiungere o aggiornare la chiave privata del tuo account.", "admin_supportInitHint": "Puoi configurare una mailbox di supporto per dare agli utenti del tuo CryptPad un modo per contattarti in maniera sicura se hanno problemi con i loro account.", "admin_supportListHint": "Questa è la lista dei ticket inviati dagli utenti alla mailbox di supporto. Tutti gli amministratori possono vedere i messaggi e le risposte. Un ticket chiuso non può essere riaperto. Puoi solo rimuovere (nascondere) i ticket chiusi, ma i ticket rimossi rimangono visibili agli altri amministratori.", "requestEdit_confirm": "{1} ha richiesto la possibilità di modificare il pad {0}. Vuoi fornirgli l'accesso?", diff --git a/www/common/translations/messages.json b/www/common/translations/messages.json index 264f76ad6..50133d1a2 100644 --- a/www/common/translations/messages.json +++ b/www/common/translations/messages.json @@ -963,7 +963,7 @@ "creation_expiration": "Expiration time", "creation_passwordValue": "Password", "creation_propertiesTitle": "Availability", - "creation_appMenuName": "Advanced mode (Ctrl + E)", + "creation_appMenuName": "New pad (Ctrl + E)", "creation_newPadModalDescription": "Click on a pad type to create it. You can also press Tab to select the type and press Enter to confirm.", "creation_newPadModalDescriptionAdvanced": "You can check the box (or press Space to change its value) if you want to display the pad creation screen (for owned pads, expiring pads, etc.).", "creation_newPadModalAdvanced": "Display the pad creation screen", diff --git a/www/kanban/inner.js b/www/kanban/inner.js index 6a8595821..51c4e9de2 100644 --- a/www/kanban/inner.js +++ b/www/kanban/inner.js @@ -133,8 +133,12 @@ define([ var PROPERTIES = ['title', 'body', 'tags', 'color']; var BOARD_PROPERTIES = ['title', 'color']; var createEditModal = function (framework, kanban) { + if (framework.isReadOnly()) { return; } + if (editModal) { return editModal; } + var dataObject = {}; var isBoard, id; + var offline = false; var update = Util.throttle(function () { kanban.setBoards(kanban.options.boards); @@ -145,7 +149,7 @@ define([ framework.localChange(); update(); }; - if (editModal) { return editModal; } + var conflicts, conflictContainer, titleInput, tagsDiv, colors, text; var content = h('div', [ conflictContainer = h('div#cp-kanban-edit-conflicts', [ @@ -165,7 +169,6 @@ define([ ]); var $tags = $(tagsDiv); - var $conflict = $(conflicts); var $cc = $(conflictContainer); var conflict = { @@ -280,6 +283,7 @@ define([ }); var commitTags = function () { + if (offline) { return; } setTimeout(function () { dataObject.tags = Util.deduplicateString(_field.getTokens().map(function (t) { return t.toLowerCase(); @@ -303,6 +307,7 @@ define([ var $color = $(h('span.cp-kanban-palette.fa')); $color.addClass('cp-kanban-palette-'+(color || 'nocolor')); $color.click(function () { + if (offline) { return; } if (color === selectedColor) { return; } selectedColor = color; $colors.find('.cp-kanban-palette').removeClass('fa-check'); @@ -361,6 +366,17 @@ define([ buttons: button }); modal.classList.add('cp-kanban-edit-modal'); + var $modal = $(modal); + + framework.onEditableChange(function (unlocked) { + editor.setOption('readOnly', !unlocked); + $title.prop('disabled', unlocked ? '' : 'disabled'); + $(_field.element).tokenfield(unlocked ? 'enable' : 'disable'); + + $modal.find('nav button.danger').prop('disabled', unlocked ? '' : 'disabled'); + offline = !unlocked; + }); + var setId = function (_isBoard, _id) { // Reset the mdoal with a new id @@ -384,7 +400,7 @@ define([ .show(); } // Also reset the buttons - $(modal).find('nav').after(UI.dialog.getButtons(button)).remove(); + $modal.find('nav').after(UI.dialog.getButtons(button)).remove(); }; onRemoteChange.reg(function () { @@ -973,6 +989,7 @@ define([ } kanban.options.readOnly = true; $container.addClass('cp-app-readonly'); + $container.find('.kanban-edit-item').remove(); }); var getCursor = function () {