From bed067f9bc05f1ef65e3158fab37dfe57fb03c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Thu, 29 Oct 2015 17:15:26 +0100 Subject: [PATCH] Add first cut on persistent stores --- js/actions/user_actions.js | 15 ++++ js/models/ascribe_storage.js | 98 +++++++++++++++++++++++++++ js/stores/session_persistent_store.js | 21 ++++++ js/stores/user_store.js | 10 ++- js/utils/feature_detection_utils.js | 7 +- js/utils/general_utils.js | 9 ++- 6 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 js/models/ascribe_storage.js create mode 100644 js/stores/session_persistent_store.js diff --git a/js/actions/user_actions.js b/js/actions/user_actions.js index 3780a802..59fdfa25 100644 --- a/js/actions/user_actions.js +++ b/js/actions/user_actions.js @@ -3,6 +3,8 @@ import { altUser } from '../alt'; import UserFetcher from '../fetchers/user_fetcher'; +import UserStore from '../stores/user_store'; + class UserActions { constructor() { @@ -22,6 +24,19 @@ class UserActions { this.actions.updateCurrentUser({}); }); } + + /*fetchCurrentUser() { + if(UserStore.getState().currentUser && !UserStore.getState().currentUser.email) { + UserFetcher.fetchOne() + .then((res) => { + this.actions.updateCurrentUser(res.users[0]); + }) + .catch((err) => { + console.logGlobal(err); + this.actions.updateCurrentUser({}); + }); + } + }*/ logoutCurrentUser() { UserFetcher.logout() diff --git a/js/models/ascribe_storage.js b/js/models/ascribe_storage.js new file mode 100644 index 00000000..9f71e243 --- /dev/null +++ b/js/models/ascribe_storage.js @@ -0,0 +1,98 @@ +'use strict'; + +import { sanitize } from '../utils/general_utils'; + +/** + * A tiny wrapper around HTML5's `webStorage`, + * to enable saving JSON objects directly into `webStorage` + */ +export default class AscribeStorage { + /** + * @param {String} `name` A unique storage name + */ + constructor(type, name) { + if(type === 'localStorage' || type === 'sessionStorage') { + this.storage = window[type]; + } else { + throw new Error('"type" can only be either "localStorage" or "sessionStorage"'); + } + + this.name = name; + } + + /** + * Private method, do not use from the outside. + * Constructs a unique identifier for a item in the global `webStorage`, + * by appending the `ÀscribeStorage`'s name + * @param {string} key A unique identifier + * @return {string} A globally unique identifier for a value in `webStorage` + */ + _constructStorageKey(key) { + return `${this.name}-${key}`; + } + + _deconstructStorageKey(key) { + return key.replace(`${this.name}-`, ''); + } + + /** + * Saves a JSON-serializeble object or a string into `webStorage` + * @param {string} key Used as a unique identifier + * @param {oneOfType([String, object])} value Either JSON-serializeble or a string + */ + setItem(key, value) { + // We're "try-catching" errors in this method ourselves, to be able to + // yield more readable error messages + + if(!key || !value) { + throw new Error('"key" or "value" cannot be "falsy" values'); + } else if(typeof value === 'string') { + // since `value` is a string, we can directly write + // it into `this.storage` + this.storage.setItem(this._constructStorageKey(key), value); + } else { + // if `value` is not a string, we need to JSON-serialize it and then + // put it into `this.storage` + + let serializedValue; + try { + serializedValue = JSON.stringify(value); + } catch(err) { + throw new Error('You didn\'t pass valid JSON as "value" into setItem.'); + } + + try { + this.storage.setItem(this._constructStorageKey(key), serializedValue); + } catch(err) { + throw new Error('Failure saving a "serializedValue" in setItem'); + } + } + } + + getItem(key) { + let deserializedValue; + const serializedValue = this.storage.getItem(this._constructStorageKey(key)); + + try { + deserializedValue = JSON.parse(serializedValue); + } catch(err) { + deserializedValue = serializedValue; + } + + return deserializedValue; + } + + toObject() { + let obj = {}; + const storageCopy = JSON.parse(JSON.stringify(this.storage)); + const sanitizedStore = sanitize(storageCopy, s => !s.match(`${this.name}-`), true); + + Object + .keys(sanitizedStore) + .forEach((key) => { + obj[this._deconstructStorageKey(key)] = JSON.parse(sanitizedStore[key]); + }); + + return obj; + } +} \ No newline at end of file diff --git a/js/stores/session_persistent_store.js b/js/stores/session_persistent_store.js new file mode 100644 index 00000000..cd2fa1ca --- /dev/null +++ b/js/stores/session_persistent_store.js @@ -0,0 +1,21 @@ +'use strict'; + +import AscribeStorage from '../models/ascribe_storage'; + + +export default class SessionPersistentStore extends AscribeStorage { + constructor(name) { + super('sessionStorage', name); + } + + setItem(key, value) { + this[key] = value; + super.setItem(key, value); + } +} + +SessionPersistentStore.config = { + getState() { + return new AscribeStorage('sessionStorage', this.displayName).toObject(); + } +}; \ No newline at end of file diff --git a/js/stores/user_store.js b/js/stores/user_store.js index 8ea18eea..dc55f0c2 100644 --- a/js/stores/user_store.js +++ b/js/stores/user_store.js @@ -3,15 +3,21 @@ import { altUser } from '../alt'; import UserActions from '../actions/user_actions'; +import SessionPersistentStore from './session_persistent_store'; -class UserStore { +// import AscribeStorage from '../models/ascribe_storage'; +// import { sessionStorageAvailable } from '../utils/feature_detection_utils'; + + +class UserStore extends SessionPersistentStore { constructor() { + super('UserStore'); this.currentUser = {}; this.bindActions(UserActions); } onUpdateCurrentUser(user) { - this.currentUser = user; + this.setItem('currentUser', user); } onDeleteCurrentUser() { this.currentUser = {}; diff --git a/js/utils/feature_detection_utils.js b/js/utils/feature_detection_utils.js index 8e113469..66e623d2 100644 --- a/js/utils/feature_detection_utils.js +++ b/js/utils/feature_detection_utils.js @@ -51,11 +51,8 @@ function storageAvailable(type) { } /** - * Function detects whether sessionStorage is both supported + * Const that detects whether sessionStorage is both supported * and available. - * @return {bool} Is sessionStorage available on this browser */ -export function sessionStorageAvailable() { - return storageAvailable('sessionStorage'); -} +export const sessionStorageAvailable = storageAvailable('sessionStorage'); diff --git a/js/utils/general_utils.js b/js/utils/general_utils.js index 7c13f9b5..07d42327 100644 --- a/js/utils/general_utils.js +++ b/js/utils/general_utils.js @@ -6,9 +6,12 @@ * tagged as false by the passed in filter function * * @param {object} obj regular javascript object + * @param {function} filterFn a filter function for filtering either by key or value + * @param {bool} filterByKey a boolean for choosing whether the object should be filtered by + * key or value * @return {object} regular javascript object without null values or empty strings */ -export function sanitize(obj, filterFn) { +export function sanitize(obj, filterFn, filterByKey) { if(!filterFn) { // By matching null with a double equal, we can match undefined and null // http://stackoverflow.com/a/15992131 @@ -18,7 +21,9 @@ export function sanitize(obj, filterFn) { Object .keys(obj) .map((key) => { - if(filterFn(obj[key])) { + const filterCondition = filterByKey ? filterFn(key) : filterFn(obj[key]); + + if(filterCondition) { delete obj[key]; } });