diff --git a/public/src/app.js b/public/src/app.js index 3e53305aac..b5b5302bd2 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -630,16 +630,17 @@ app.cacheBuster = null; }; app.showCookieWarning = function () { - if (!config.cookies.enabled || !navigator.cookieEnabled) { - // Skip warning if cookie consent subsystem disabled (obviously), or cookies not in use - return; - } else if (window.location.pathname.startsWith(config.relative_path + '/admin')) { - // No need to show cookie consent warning in ACP - return; - } else if (window.localStorage.getItem('cookieconsent') === '1') { - return; - } - require(['translator'], function (translator) { + require(['translator', 'storage'], function (translator, storage) { + if (!config.cookies.enabled || !navigator.cookieEnabled) { + // Skip warning if cookie consent subsystem disabled (obviously), or cookies not in use + return; + } else if (window.location.pathname.startsWith(config.relative_path + '/admin')) { + // No need to show cookie consent warning in ACP + return; + } else if (storage.getItem('cookieconsent') === '1') { + return; + } + config.cookies.message = translator.unescape(config.cookies.message); config.cookies.dismiss = translator.unescape(config.cookies.dismiss); config.cookies.link = translator.unescape(config.cookies.link); @@ -651,7 +652,7 @@ app.cacheBuster = null; var dismissEl = warningEl.find('button'); dismissEl.on('click', function () { // Save consent cookie and remove warning element - window.localStorage.setItem('cookieconsent', '1'); + storage.setItem('cookieconsent', '1'); warningEl.remove(); }); }); diff --git a/public/src/client/category.js b/public/src/client/category.js index e2dfcd9924..7fea7cfbcb 100644 --- a/public/src/client/category.js +++ b/public/src/client/category.js @@ -11,7 +11,8 @@ define('forum/category', [ 'translator', 'topicSelect', 'forum/pagination', -], function (infinitescroll, share, navigator, categoryTools, sort, components, translator, topicSelect, pagination) { + 'storage', +], function (infinitescroll, share, navigator, categoryTools, sort, components, translator, topicSelect, pagination, storage) { var Category = {}; $(window).on('action:ajaxify.start', function (ev, data) { @@ -51,8 +52,8 @@ define('forum/category', [ var clickedIndex = $(this).parents('[data-index]').attr('data-index'); $('[component="category/topic"]').each(function (index, el) { if ($(el).offset().top - $(window).scrollTop() > 0) { - localStorage.setItem('category:' + cid + ':bookmark', $(el).attr('data-index')); - localStorage.setItem('category:' + cid + ':bookmark:clicked', clickedIndex); + storage.setItem('category:' + cid + ':bookmark', $(el).attr('data-index')); + storage.setItem('category:' + cid + ':bookmark:clicked', clickedIndex); return false; } }); @@ -118,8 +119,8 @@ define('forum/category', [ $(window).on('action:ajaxify.contentLoaded', function () { if (ajaxify.data.template.category && ajaxify.data.cid) { - var bookmarkIndex = localStorage.getItem('category:' + ajaxify.data.cid + ':bookmark'); - var clickedIndex = localStorage.getItem('category:' + ajaxify.data.cid + ':bookmark:clicked'); + var bookmarkIndex = storage.getItem('category:' + ajaxify.data.cid + ':bookmark'); + var clickedIndex = storage.getItem('category:' + ajaxify.data.cid + ':bookmark:clicked'); bookmarkIndex = Math.max(0, parseInt(bookmarkIndex, 10) || 0); clickedIndex = Math.max(0, parseInt(clickedIndex, 10) || 0); diff --git a/public/src/client/search.js b/public/src/client/search.js index 4f65935913..de68d1e401 100644 --- a/public/src/client/search.js +++ b/public/src/client/search.js @@ -1,7 +1,7 @@ 'use strict'; -define('forum/search', ['search', 'autocomplete'], function (searchModule, autocomplete) { +define('forum/search', ['search', 'autocomplete', 'storage'], function (searchModule, autocomplete, storage) { var Search = {}; Search.init = function () { @@ -147,13 +147,13 @@ define('forum/search', ['search', 'autocomplete'], function (searchModule, autoc function handleSavePreferences() { $('#save-preferences').on('click', function () { - localStorage.setItem('search-preferences', JSON.stringify(getSearchData())); + storage.setItem('search-preferences', JSON.stringify(getSearchData())); app.alertSuccess('[[search:search-preferences-saved]]'); return false; }); $('#clear-preferences').on('click', function () { - localStorage.removeItem('search-preferences'); + storage.removeItem('search-preferences'); var query = $('#search-input').val(); $('#advanced-search')[0].reset(); $('#search-input').val(query); diff --git a/public/src/client/topic.js b/public/src/client/topic.js index d620da1e70..e4ba5a06a5 100644 --- a/public/src/client/topic.js +++ b/public/src/client/topic.js @@ -12,7 +12,8 @@ define('forum/topic', [ 'navigator', 'sort', 'components', -], function (infinitescroll, threadTools, postTools, events, posts, images, replies, navigator, sort, components) { + 'storage', +], function (infinitescroll, threadTools, postTools, events, posts, images, replies, navigator, sort, components, storage) { var Topic = {}; var currentUrl = ''; @@ -142,7 +143,7 @@ define('forum/topic', [ function handleBookmark(tid) { // use the user's bookmark data if available, fallback to local if available - var bookmark = ajaxify.data.bookmark || localStorage.getItem('topic:' + tid + ':bookmark'); + var bookmark = ajaxify.data.bookmark || storage.getItem('topic:' + tid + ':bookmark'); var postIndex = getPostIndex(); if (postIndex && window.location.search.indexOf('page=') === -1) { @@ -160,7 +161,7 @@ define('forum/topic', [ navigator.scrollToPost(parseInt(bookmark - 1, 10), true); }, closefn: function () { - localStorage.removeItem('topic:' + tid + ':bookmark'); + storage.removeItem('topic:' + tid + ':bookmark'); }, }); setTimeout(function () { @@ -273,7 +274,7 @@ define('forum/topic', [ function updateUserBookmark(index) { var bookmarkKey = 'topic:' + ajaxify.data.tid + ':bookmark'; - var currentBookmark = ajaxify.data.bookmark || localStorage.getItem(bookmarkKey); + var currentBookmark = ajaxify.data.bookmark || storage.getItem(bookmarkKey); if (ajaxify.data.postcount > ajaxify.data.bookmarkThreshold && (!currentBookmark || parseInt(index, 10) > parseInt(currentBookmark, 10))) { if (app.user.uid) { @@ -287,7 +288,7 @@ define('forum/topic', [ ajaxify.data.bookmark = index; }); } else { - localStorage.setItem(bookmarkKey, index); + storage.setItem(bookmarkKey, index); } } diff --git a/public/src/modules/search.js b/public/src/modules/search.js index 1401bf8619..5b77ab7572 100644 --- a/public/src/modules/search.js +++ b/public/src/modules/search.js @@ -1,7 +1,7 @@ 'use strict'; -define('search', ['navigator', 'translator'], function (nav, translator) { +define('search', ['navigator', 'translator', 'storage'], function (nav, translator, storage) { var Search = { current: {}, }; @@ -79,7 +79,7 @@ define('search', ['navigator', 'translator'], function (nav, translator) { Search.getSearchPreferences = function () { try { - return JSON.parse(localStorage.getItem('search-preferences') || '{}'); + return JSON.parse(storage.getItem('search-preferences') || '{}'); } catch (e) { return {}; } diff --git a/public/src/modules/sounds.js b/public/src/modules/sounds.js index 38bbaec9cb..9a5f560447 100644 --- a/public/src/modules/sounds.js +++ b/public/src/modules/sounds.js @@ -1,7 +1,7 @@ 'use strict'; -define('sounds', function () { +define('sounds', ['storage'], function (storage) { var Sounds = {}; var fileMap; @@ -67,13 +67,13 @@ define('sounds', function () { if (id) { var item = 'sounds.handled:' + id; - if (localStorage.getItem(item)) { + if (storage.getItem(item)) { return; } - localStorage.setItem(item, true); + storage.setItem(item, true); setTimeout(function () { - localStorage.removeItem(item); + storage.removeItem(item); }, 5000); } diff --git a/public/src/modules/storage.js b/public/src/modules/storage.js new file mode 100644 index 0000000000..5cb6051586 --- /dev/null +++ b/public/src/modules/storage.js @@ -0,0 +1,84 @@ +'use strict'; + +/** + * Checks localStorage and provides a fallback if it doesn't exist or is disabled + */ +define('storage', function () { + function Storage() { + this._store = {}; + this._keys = []; + } + Storage.prototype.isMock = true; + Storage.prototype.setItem = function (key, val) { + key = String(key); + if (this._keys.indexOf(key) === -1) { + this._keys.push(key); + } + this._store[key] = val; + }; + Storage.prototype.getItem = function (key) { + key = String(key); + if (this._keys.indexOf(key) === -1) { + return null; + } + + return this._store[key]; + }; + Storage.prototype.removeItem = function (key) { + key = String(key); + this._keys = this._keys.filter(function (x) { + return x !== key; + }); + this._store[key] = null; + }; + Storage.prototype.clear = function () { + this._keys = []; + this._store = {}; + }; + Storage.prototype.key = function (n) { + n = parseInt(n, 10) || 0; + return this._keys[n]; + }; + if (Object.defineProperty) { + Object.defineProperty(Storage.prototype, 'length', { + get: function () { + return this._keys.length; + }, + }); + } + + var storage; + var item = Date.now(); + + try { + storage = window.localStorage; + storage.setItem(item, item); + if (storage.getItem(item) !== item) { + throw Error('localStorage behaved unexpectedly'); + } + storage.removeItem(item); + + return storage; + } catch (e) { + console.warn(e); + console.warn('localStorage failed, falling back on sessionStorage'); + + // see if sessionStorage works, and if so, return that + try { + storage = window.sessionStorage; + storage.setItem(item, item); + if (storage.getItem(item) !== item) { + throw Error('sessionStorage behaved unexpectedly'); + } + storage.removeItem(item); + + return storage; + } catch (e) { + console.warn(e); + console.warn('sessionStorage failed, falling back on memory storage'); + + // return an object implementing mock methods + return new Storage(); + } + } +}); diff --git a/src/meta/js.js b/src/meta/js.js index 967b100a32..0fff87c6a1 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -78,6 +78,7 @@ module.exports = function (Meta) { 'public/src/modules/taskbar.js', 'public/src/modules/helpers.js', 'public/src/modules/string.js', + 'public/src/modules/storage.js', ], // modules listed below are built (/src/modules) so they can be defined anonymously