diff --git a/docs/options.md b/docs/options.md index 9e1f3cff2..cddc6bd81 100644 --- a/docs/options.md +++ b/docs/options.md @@ -90,8 +90,16 @@ Otherwise, defaults to the language set for `locale`. ## storeLocale -If set to `true`, stores the locale selected by the user in the `localStorage` of the browser. -Otherwise, doesn't store the locale across browser sessions. +If set to `true` (default), stores the locale selected by the user in the storage of the browser. +If set to `false`, doesn't store the locale across browser sessions. + +Depending on the browser settings, this may store in either: +- `localeStorage` +- `sessionStorage` +- cookies + +In some countries this may have implications with regards to GDPR etc. +If you want to avoid this, disable this setting. ## locale diff --git a/src/StacBrowser.vue b/src/StacBrowser.vue index 0f3fa1552..adec63e56 100644 --- a/src/StacBrowser.vue +++ b/src/StacBrowser.vue @@ -45,6 +45,7 @@ import URI from 'urijs'; import I18N from '@radiantearth/stac-fields/I18N'; import { translateFields, API_LANGUAGE_CONFORMANCE, loadMessages } from './i18n'; import { getBest, prepareSupported } from './locale-id'; +import BrowserStorage from "./browser-store"; Vue.use(AlertPlugin); Vue.use(ButtonGroupPlugin); @@ -285,11 +286,8 @@ export default { detectLocale() { let locale; if (this.storeLocaleFromVueX) { - try { - locale = window.localStorage.getItem('locale'); - } catch(error) { - console.error(error); - } + const storage = new BrowserStorage(); + locale = storage.get('locale'); } if (!locale && this.detectLocaleFromBrowserFromVueX && Array.isArray(navigator.languages)) { // Detect the most suitable locale diff --git a/src/browser-store.js b/src/browser-store.js new file mode 100644 index 000000000..ef8dfab59 --- /dev/null +++ b/src/browser-store.js @@ -0,0 +1,156 @@ +import Utils from "./utils"; + +export default class BrowserStorage { + + static JSON_INDICATOR = "\n\r"; + + static enabled(engine) { + if (!Utils.isObject(engine)) { + return false; + } + try { + engine.setItem('test', 'yes'); + if (engine.getItem('test') === 'yes') { + engine.removeItem('test'); + return true; + } + } catch(error) { + console.error(error); + } + return false; + } + + constructor(session = false) { + if (session) { + if (BrowserStorage.enabled(window.sessionStorage)) { + this.engine = window.sessionStorage; + } + else if (navigator.cookieEnabled) { + this.engine = new Cookies(true); + } + else { + this.engine = new NoOp(); + } + } + else { + if (BrowserStorage.enabled(window.localStorage)) { + this.engine = window.localStorage; + } + else if (navigator.cookieEnabled) { + this.engine = new Cookies(); + } + else { + this.engine = new NoOp(); + } + } + } + + get(name) { + try { + let data = this.engine.getItem(name); + if (typeof data === 'string' && data.startsWith(BrowserStorage.JSON_INDICATOR)) { + data = JSON.parse(data.slice(BrowserStorage.JSON_INDICATOR.length)); + } + return data; + } catch(error) { + console.error(error); + return null; + } + } + + set(name, value) { + try { + if (typeof value !== 'string') { + value = BrowserStorage.JSON_INDICATOR + JSON.stringify(value); + } + this.engine.setItem(name, value); + } catch(error) { + console.error(error); + } + } + + remove(name) { + try { + this.engine.removeItem(name); + } catch(error) { + console.error(error); + } + } + + clear() { + this.engine.clear(); + } + +} + +class Cookies { + + constructor(session = false) { + this.session = session; + } + + getExpiry(minutes = null) { + if (minutes === null) { + if (this.session) { + minutes = 60; // 60 minutes + } + else { + minutes = 1000 * 24 * 60; // 1000 days + } + } + const date = new Date(); + date.setTime(date.getTime() + minutes * 60 * 1000); + this.epires = date.toGMTString(); + } + + setItem(name, value, minutes = null) { + const expires = this.getExpiry(minutes); + value = encodeURIComponent(value); + document.cookie = `${name}=${value}; expires=${expires}; path=/`; + } + + getItem(name) { + const prefix = name + "="; + const parts = document.cookie.split(';'); + for (let c of parts) { + c = c.trim(); + if (c.startsWith(prefix)) { + const data = c.substring(prefix.length, c.length); + return decodeURIComponent(data); + } + } + return null; + } + + removeItem(name) { + this.set(name, "", -1); + } + + clear() { + document.cookie = ''; + } + +} + + +class NoOp { + + constructor(session = false) { + this.session = session; + } + + setItem(name/*, value*/) { + console.warn(`Browser storage disabled, can't store ${name}`); + } + + getItem(/*name*/) { + return null; + } + + removeItem(/*name*/) { + } + + clear() { + } + +} diff --git a/src/store/index.js b/src/store/index.js index 94cbf85a0..a49d44cd9 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -11,6 +11,7 @@ import STAC from '../models/stac'; import { addQueryIfNotExists, isAuthenticationError, Loading, processSTAC, proxyUrl, unproxyUrl, stacRequest } from './utils'; import { getBest } from '../locale-id'; import { TYPES } from "../components/ApiCapabilitiesMixin"; +import BrowserStorage from "../browser-store.js"; function getStore(config, router) { // Local settings (e.g. for currently loaded STAC entity) @@ -634,11 +635,8 @@ function getStore(config, router) { await cx.dispatch('config', {locale}); if (cx.state.storeLocale && userSelected) { - try { - window.localStorage.setItem('locale', locale); - } catch (error) { - console.error(error); - } + const storage = new BrowserStorage(); + locale = storage.set('locale', locale); } // Locale for UI