diff --git a/lib/hk-util.js b/lib/hk-util.js index 4fa2140f9..20e0ce5d4 100644 --- a/lib/hk-util.js +++ b/lib/hk-util.js @@ -440,9 +440,11 @@ const getHistoryOffset = (Env, channelName, lastKnownHash, _cb) => { // If our lastKnownHash is older than the 2nd to last checkpoint, // only send the last 2 checkpoints and ignore "lkh" - if (lkh && index.cpIndex.length >= 2 && lkh < index.cpIndex[0].offset) { + // XXX XXX this is probably wrong! ChainPad may not accept checkpoints that are not connected to root + // XXX We probably need to send an EUNKNOWN here so that the client can recreate a new chainpad + /*if (lkh && index.cpIndex.length >= 2 && lkh < index.cpIndex[0].offset) { return void cb(null, index.cpIndex[0].offset); - } + }*/ // Otherwise use our lastKnownHash cb(null, lkh); diff --git a/www/common/media-tag.js b/www/common/media-tag.js index 27683c54e..1d7cd0c61 100644 --- a/www/common/media-tag.js +++ b/www/common/media-tag.js @@ -1,8 +1,5 @@ -(function(name, definition) { - if (typeof module !== 'undefined') { module.exports = definition(); } - else if (typeof define === 'function' && typeof define.amd === 'object') { define(definition); } - else { this[name] = definition(); } -}('MediaTag', function() { +(function (window) { +var factory = function (Cache) { var cache; var cypherChunkLength = 131088; @@ -133,20 +130,44 @@ cb = function () {}; }; - var xhr = new XMLHttpRequest(); - xhr.open('GET', src, true); - xhr.responseType = 'arraybuffer'; + var _src = src.replace(/(\/)*$/, ''); // Remove trailing slashes + var idx = _src.lastIndexOf('/'); + var cacheKey = _src.slice(idx+1); + if (!/^[a-f0-9]{48}$/.test(cacheKey)) { cacheKey = undefined; } - xhr.onerror = function () { return void cb("XHR_ERROR"); }; - xhr.onload = function () { - // Error? - if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); } + var fetch = function () { + var xhr = new XMLHttpRequest(); + xhr.open('GET', src, true); + xhr.responseType = 'arraybuffer'; - var arrayBuffer = xhr.response; - if (arrayBuffer) { cb(null, new Uint8Array(arrayBuffer)); } + xhr.onerror = function () { return void cb("XHR_ERROR"); }; + xhr.onload = function () { + // Error? + if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); } + + var arrayBuffer = xhr.response; + if (arrayBuffer) { + var u8 = new Uint8Array(arrayBuffer); + if (cacheKey) { + return void Cache.setBlobCache(cacheKey, u8, function (err) { + cb(null, u8); + }); + } + cb(null, u8); + } + }; + + xhr.send(null); }; - xhr.send(null); + if (!cacheKey) { return void fetch(); } + + Cache.getBlobCache(cacheKey, function (err, u8) { + if (err || !u8) { return void fetch(); } + console.error('using cache', cacheKey); + cb(null, u8); + }); + }; // Decryption tools @@ -469,4 +490,19 @@ }; return init; -})); +}; + + if (typeof(module) !== 'undefined' && module.exports) { + module.exports = factory( + require("./outer/cache-store.js") + ); + } else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) { + define([ + '/common/outer/cache-store.js', + ], function (Cache) { + return factory(Cache); + }); + } else { + // unsupported initialization + } +}(typeof(window) !== 'undefined'? window : {})); diff --git a/www/common/outer/cache-store.js b/www/common/outer/cache-store.js index 56c78a5be..fe3b541b2 100644 --- a/www/common/outer/cache-store.js +++ b/www/common/outer/cache-store.js @@ -8,6 +8,28 @@ define([ name: "cp_cache" }); + S.getBlobCache = function (id, cb) { + cb = Util.once(Util.mkAsync(cb || function () {})); + cache.getItem(id, function (err, obj) { + if (err || !obj || !obj.c) { + return void cb(err || 'EINVAL'); + } + cb(null, obj.c); + obj.t = +new Date(); + cache.setItem(id, obj); + }); + }; + S.setBlobCache = function (id, u8, cb) { + cb = Util.once(Util.mkAsync(cb || function () {})); + if (!u8) { return void cb('EINVAL'); } + cache.setItem(id, { + c: u8, + t: (+new Date()) // 't' represent the "lastAccess" of this cache (get or set) + }, function (err) { + cb(err); + }); + }; + // id: channel ID or blob ID // returns array of messages S.getChannelCache = function (id, cb) { @@ -55,7 +77,9 @@ define([ S.clearChannel = function (id, _cb) { cb = Util.once(Util.mkAsync(_cb || function () {})); - cache.removeItem(id, cb); + cache.removeItem(id, function () { + cb(); + }); }; S.clear = function () { diff --git a/www/common/sframe-app-framework.js b/www/common/sframe-app-framework.js index 6353fb2f8..50565cc0b 100644 --- a/www/common/sframe-app-framework.js +++ b/www/common/sframe-app-framework.js @@ -467,9 +467,14 @@ define([ }); }; - var onCorruptedCache = function (cb) { - var sframeChan = common.getSframeChannel(); - sframeChan.event("Q_CORRUPTED_CACHE", cb); + var noCache = false; // Prevent reload loops + var onCorruptedCache = function () { + if (noCache) { + // XXX translation key + return UI.errorLoadingScreen("Reload loop: empty chainpad for a non-empty channel"); + } + noCache = true; + sframeChan.event("Q_CORRUPTED_CACHE"); }; var onCacheReady = function () { stateChange(STATE.DISCONNECTED); @@ -547,17 +552,17 @@ define([ Feedback.send("NON_EMPTY_NEWDOC"); // 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); - }); + onCorruptedCache(); return; } - console.log('updating title'); title.updateTitle(title.defaultTitle); evOnDefaultContentNeeded.fire(); } }).nThen(function () { + // We have a valid chainpad, reenable cache fix in case with reconnect with + // a corrupted cache + noCache = false; + stateChange(STATE.READY); firstConnection = false; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index fe32a6f8a..059dc90e4 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1638,8 +1638,7 @@ define([ }); }; - sframeChan.on('Q_CORRUPTED_CACHE', function (data, cb) { - Utils.Cache.clearChannel(secret.channel, cb); + sframeChan.on('EV_CORRUPTED_CACHE', function () { Cryptpad.onCorruptedCache(secret.channel); });