From e998c21691aa302ce9d0523f638d54403c43168e Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 5 Nov 2020 16:19:05 +0100 Subject: [PATCH 1/2] Invalidate corrupted caches --- www/common/cryptpad-common.js | 4 +++ www/common/outer/async-store.js | 10 +++++- www/common/outer/cache-store.js | 37 ++++++++++++--------- www/common/outer/store-rpc.js | 1 + www/common/sframe-app-framework.js | 23 ++++++++----- www/common/sframe-chainpad-netflux-inner.js | 6 ---- www/common/sframe-chainpad-netflux-outer.js | 2 -- www/common/sframe-common-outer.js | 9 ++++- 8 files changed, 58 insertions(+), 34 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 82ea2991f..34272dcfa 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1006,6 +1006,10 @@ define([ postMessage("GIVE_PAD_ACCESS", data, cb); }; + common.onCorruptedCache = function (channel) { + postMessage("CORRUPTED_CACHE", channel); + }; + common.setPadMetadata = function (data, cb) { postMessage('SET_PAD_METADATA', data, cb); }; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 7066d3921..56e1cadfd 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1607,7 +1607,7 @@ define([ if (padData && padData.validateKey && store.messenger) { store.messenger.storeValidateKey(data.channel, padData.validateKey); } - postMessage(clientId, "PAD_READY"); + postMessage(clientId, "PAD_READY", pad.noCache); }, onMessage: function (m, user, validateKey, isCp, hash) { channel.lastHash = hash; @@ -1738,6 +1738,14 @@ define([ channel.sendMessage(msg, clientId, cb); }; + Store.corruptedCache = function (clientId, channel) { + var chan = channels[channel]; + if (!chan || !chan.cpNf) { return; } + Cache.clearChannel(channel); + if (!chan.cpNf.resetCache) { return; } + chan.cpNf.resetCache(); + }; + // Unpin and pin the new channel in all team when changing a pad password Store.changePadPasswordPin = function (clientId, data, cb) { var oldChannel = data.oldChannel; diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index aed480860..687259ddd 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -1,7 +1,7 @@ define([ - // XXX Load util and use mkAsync + '/common/common-util.js', '/bower_components/localforage/dist/localforage.min.js', -], function (localForage) { +], function (Util, localForage) { var S = {}; var cache = localForage.createInstance({ @@ -11,43 +11,50 @@ define([ // id: channel ID or blob ID // returns array of messages S.getChannelCache = function (id, cb) { + cb = Util.once(Util.mkAsync(cb || function () {})); cache.getItem(id, function (err, obj) { if (err || !obj || !Array.isArray(obj.c)) { return void cb(err || 'EINVAL'); } cb(null, obj); + obj.t = +new Date(); + cache.setItem(id, obj); }); }; + // Keep the last two checkpoint + any checkpoint that may exist in the last 100 messages + // FIXME: duplicate system with sliceCpIndex from lib/hk-util.js var checkCheckpoints = function (array) { if (!Array.isArray(array)) { return; } - var cp = 0; - // XXX check sliceCpIndex from hk-util: use the same logic for forks - for (var i = array.length - 1; i >= 0; i--) { - if (array[i].isCheckpoint) { cp++; } - if (cp === 2) { - array.splice(0, i); - break; - } + // Keep the last 100 messages + if (array.length > 100) { + array.splice(0, array.length - 100); } + // Remove every message before the first checkpoint + var firstCpIdx; + array.some(function (el, i) { + if (!el.isCheckpoint) { return; } + firstCpIdx = i; + return true; + }); + array.splice(0, firstCpIdx); }; S.storeCache = function (id, validateKey, val, cb) { - cb = cb || function () {}; + cb = Util.once(Util.mkAsync(cb || function () {})); if (!Array.isArray(val) || !validateKey) { return void cb('EINVAL'); } checkCheckpoints(val); cache.setItem(id, { k: validateKey, - c: val - // XXX add "time" here +new Date() ==> we want to store the time when it was updated in our indexeddb in case we want to remove inactive entries from indexeddb later - // XXX we would also need to update the time when we "getChannelCache" + c: val, + t: (+new Date()) // 't' represent the "lastAccess" of this cache (get or set) }, function (err) { cb(err); }); }; S.clearChannel = function (id, cb) { - cb = cb || function () {}; + cb = Util.once(Util.mkAsync(cb || function () {})); cache.removeItem(id, cb); }; diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 6ac3e52b0..767448284 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -88,6 +88,7 @@ define([ CHANGE_PAD_PASSWORD_PIN: Store.changePadPasswordPin, GET_LAST_HASH: Store.getLastHash, GET_SNAPSHOT: Store.getSnapshot, + CORRUPTED_CACHE: Store.corruptedCache, // Drive DRIVE_USEROBJECT: Store.userObjectCommand, // Settings, diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 81ab18d45..59dd51520 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -467,6 +467,10 @@ define([ }); }; + var onCorruptedCache = function (cb) { + var sframeChan = common.getSframeChannel(); + sframeChan.event("Q_CORRUPTED_CACHE", cb); + }; var onCacheReady = function () { stateChange(STATE.DISCONNECTED); toolbar.offline(true); @@ -475,23 +479,19 @@ define([ // Check if we have a new chainpad instance toolbar.resetChainpad(cpNfInner.chainpad); } -console.log(newContentStr); - // Invalid cache? abort - // XXX tell outer/worker to invalidate cache - if (newContentStr === '') { return; } + // Invalid cache + if (newContentStr === '') { return void onCorruptedCache(); } var privateDat = cpNfInner.metadataMgr.getPrivateData(); var type = privateDat.app; var newContent = JSON.parse(newContentStr); var metadata = extractMetadata(newContent); -console.log('OKOK'); // Make sure we're using the correct app for this cache if (metadata && typeof(metadata.type) !== 'undefined' && metadata.type !== type) { - console.error('return'); - return; + return void onCorruptedCache(); } cpNfInner.metadataMgr.updateMetadata(metadata); @@ -504,7 +504,7 @@ console.log('OKOK'); }; var onReady = function () { toolbar.offline(false); - console.error('READY'); + var newContentStr = cpNfInner.chainpad.getUserDoc(); if (state === STATE.DELETED) { return; } @@ -545,7 +545,12 @@ console.log('OKOK'); console.log("Either this is an empty document which has not been touched"); console.log("Or else something is terribly wrong, reloading."); Feedback.send("NON_EMPTY_NEWDOC"); - setTimeout(function () { common.gotoURL(); }, 1000); + // The cache may be wrong, empty it and reload after. + waitFor.abort(); + UI.errorLoadingScreen("MAYBE CORRUPTED CACHE... RELOADING"); // XXX + onCorruptedCache(function () { + setTimeout(function () { common.gotoURL(); }, 1000); + }); return; } console.log('updating title'); diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index 57689b7ea..e70a5874e 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -94,13 +94,8 @@ define([ evInfiniteSpinner.fire(); }, 2000); - sframeChan.on('EV_RT_CACHE', function (isPermanent) { - // XXX - }); sframeChan.on('EV_RT_CACHE_READY', function (isPermanent) { - // XXX onCacheReady({realtime: chainpad}); - console.error('PEWPEWPEW'); }); sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) { isReady = false; @@ -143,7 +138,6 @@ define([ evConnected.fire(); }); sframeChan.on('Q_RT_MESSAGE', function (content, cb) { - console.log(content); if (isReady) { onLocal(true); // should be onBeforeMessage } diff --git a/www/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js index 056b9cd28..c19299da2 100644 --- a/www/common/sframe-chainpad-netflux-outer.js +++ b/www/common/sframe-chainpad-netflux-outer.js @@ -89,7 +89,6 @@ define([], function () { validateKey = msgObj.validateKey; } var message = msgIn(msgObj.user, msgObj.msg); - console.log(message); if (!message) { return; } lastTime = msgObj.time; @@ -127,7 +126,6 @@ define([], function () { }); padRpc.onCacheReadyEvent.reg(function () { - console.log('ONCACHEREADY'); sframeChan.event('EV_RT_CACHE_READY'); }); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index b7ce64d4d..8fd37db4b 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -53,12 +53,13 @@ define([ '/common/common-constants.js', '/common/common-feedback.js', '/common/outer/local-store.js', + '/common/outer/cache-store.js', '/customize/application_config.js', '/common/test.js', '/common/userObject.js', ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel, _SecureIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime, - _Constants, _Feedback, _LocalStore, _AppConfig, _Test, _UserObject) { + _Constants, _Feedback, _LocalStore, _Cache, _AppConfig, _Test, _UserObject) { CpNfOuter = _CpNfOuter; Cryptpad = _Cryptpad; Crypto = Utils.Crypto = _Crypto; @@ -73,6 +74,7 @@ define([ Utils.Constants = _Constants; Utils.Feedback = _Feedback; Utils.LocalStore = _LocalStore; + Utils.Cache = _Cache; Utils.UserObject = _UserObject; AppConfig = _AppConfig; Test = _Test; @@ -1635,6 +1637,11 @@ define([ }); }; + sframeChan.on('Q_CORRUPTED_CACHE', function (cb) { + Utils.Cache.clearChannel(secret.channel, cb); + Cryptpad.onCorruptedCache(secret.channel); + }); + sframeChan.on('Q_CREATE_PAD', function (data, cb) { if (!isNewFile || rtStarted) { return; } // Create a new hash From e37cf4b59cbdefc3009f8e82062ae4d7defef3a1 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 5 Nov 2020 16:50:20 +0100 Subject: [PATCH 2/2] lint compliance --- www/common/outer/async-store.js | 2 +- www/common/outer/cache-store.js | 4 ++-- www/common/sframe-app-framework.js | 2 +- www/common/sframe-chainpad-netflux-inner.js | 2 +- www/common/sframe-common-outer.js | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 56e1cadfd..687326139 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1598,7 +1598,7 @@ define([ onCacheStart: function () { postMessage(clientId, "PAD_CACHE"); }, - onCacheReady: function (info) { + onCacheReady: function () { postMessage(clientId, "PAD_CACHE_READY"); }, onReady: function (pad) { diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index 687259ddd..56c78a5be 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -53,8 +53,8 @@ define([ }); }; - S.clearChannel = function (id, cb) { - cb = Util.once(Util.mkAsync(cb || function () {})); + S.clearChannel = function (id, _cb) { + cb = Util.once(Util.mkAsync(_cb || function () {})); cache.removeItem(id, cb); }; diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 59dd51520..6353fb2f8 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -497,7 +497,7 @@ define([ cpNfInner.metadataMgr.updateMetadata(metadata); newContent = normalize(newContent); if (!unsyncMode) { - contentUpdate(newContent, function () { return function () {}}); + contentUpdate(newContent, function () { return function () {}; }); } UI.removeLoadingScreen(emitResize); diff --git a/www/common/sframe-chainpad-netflux-inner.js b/www/common/sframe-chainpad-netflux-inner.js index e70a5874e..1bdcc260f 100644 --- a/www/common/sframe-chainpad-netflux-inner.js +++ b/www/common/sframe-chainpad-netflux-inner.js @@ -94,7 +94,7 @@ define([ evInfiniteSpinner.fire(); }, 2000); - sframeChan.on('EV_RT_CACHE_READY', function (isPermanent) { + sframeChan.on('EV_RT_CACHE_READY', function () { onCacheReady({realtime: chainpad}); }); sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 8fd37db4b..4c448e822 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1637,7 +1637,7 @@ define([ }); }; - sframeChan.on('Q_CORRUPTED_CACHE', function (cb) { + sframeChan.on('Q_CORRUPTED_CACHE', function (data, cb) { Utils.Cache.clearChannel(secret.channel, cb); Cryptpad.onCorruptedCache(secret.channel); });