Remove global dependency on Storage

This commit is contained in:
Chi Vinh Le
2017-08-23 19:53:06 +07:00
parent 0811c9302d
commit bf89409f60
10 changed files with 117 additions and 132 deletions
+15 -13
View File
@@ -1,6 +1,5 @@
import bowser from 'bowser';
import * as actions from '../constants/auth';
import * as Storage from 'coral-framework/helpers/storage';
import t from 'coral-framework/services/i18n';
import jwtDecode from 'jwt-decode';
@@ -8,7 +7,7 @@ import jwtDecode from 'jwt-decode';
// SIGN IN
//==============================================================================
export const handleLogin = (email, password, recaptchaResponse) => (dispatch, _, {rest, client}) => {
export const handleLogin = (email, password, recaptchaResponse) => (dispatch, _, {rest, client, storage}) => {
dispatch({type: actions.LOGIN_REQUEST});
const params = {
@@ -29,8 +28,8 @@ export const handleLogin = (email, password, recaptchaResponse) => (dispatch, _,
.then(({user, token}) => {
if (!user) {
if (!bowser.safari && !bowser.ios) {
Storage.removeItem('token');
if (!bowser.safari && !bowser.ios && storage) {
storage.removeItem('token');
}
return dispatch(checkLoginFailure('not logged in'));
}
@@ -114,13 +113,13 @@ const checkLoginFailure = (error) => ({
error
});
export const checkLogin = () => (dispatch, _, {rest, client}) => {
export const checkLogin = () => (dispatch, _, {rest, client, storage}) => {
dispatch(checkLoginRequest());
return rest('/auth')
.then(({user}) => {
if (!user) {
if (!bowser.safari && !bowser.ios) {
Storage.removeItem('token');
if (!bowser.safari && !bowser.ios && storage) {
storage.removeItem('token');
}
return dispatch(checkLoginFailure('not logged in'));
}
@@ -139,9 +138,11 @@ export const checkLogin = () => (dispatch, _, {rest, client}) => {
// LOGOUT
//==============================================================================
export const logout = () => (dispatch, _, {rest, client}) => {
export const logout = () => (dispatch, _, {rest, client, storage}) => {
return rest('/auth', {method: 'DELETE'}).then(() => {
Storage.removeItem('token');
if (storage) {
storage.removeItem('token');
}
// Reset the websocket.
client.resetWebsocket();
@@ -154,10 +155,11 @@ export const logout = () => (dispatch, _, {rest, client}) => {
// AUTH TOKEN
//==============================================================================
export const handleAuthToken = (token) => (dispatch) => {
Storage.setItem('exp', jwtDecode(token).exp);
Storage.setItem('token', token);
export const handleAuthToken = (token) => (dispatch, _, {storage}) => {
if (storage) {
storage.setItem('exp', jwtDecode(token).exp);
storage.setItem('token', token);
}
dispatch({type: 'HANDLE_AUTH_TOKEN'});
};
+5 -3
View File
@@ -4,15 +4,17 @@ export const toggleModal = (open) => ({type: actions.TOGGLE_MODAL, open});
export const singleView = () => ({type: actions.SINGLE_VIEW});
// hide shortcuts note
export const hideShortcutsNote = () => {
export const hideShortcutsNote = () => (dispatch, _, {storage}) => {
try {
window.localStorage.setItem('coral:shortcutsNote', 'hide');
if (storage) {
storage.setItem('coral:shortcutsNote', 'hide');
}
} catch (e) {
// above will fail in Safari private mode
}
return {type: actions.HIDE_SHORTCUTS_NOTE};
dispatch({type: actions.HIDE_SHORTCUTS_NOTE});
};
export const setSortOrder = (order) => ({
+10
View File
@@ -10,12 +10,22 @@ import graphqlExtension from './graphql';
import pluginsConfig from 'pluginsConfig';
import {toast} from 'react-toastify';
import {createNotificationService} from './services/notification';
import {hideShortcutsNote} from './actions/moderation';
function hidrateStore({store, storage}) {
if (storage && storage.getItem('coral:shortcutsNote') === 'hide') {
store.dispatch(hideShortcutsNote());
}
}
smoothscroll.polyfill();
const notification = createNotificationService(toast);
const context = createContext({reducers, graphqlExtension, pluginsConfig, notification});
// hidrate Store with external data.
hidrateStore(context);
render(
<TalkProvider {...context}>
<App />
@@ -5,14 +5,17 @@ const initialState = {
modalOpen: false,
storySearchVisible: false,
storySearchString: '',
shortcutsNoteVisible: window.localStorage.getItem('coral:shortcutsNote') || 'show',
shortcutsNoteVisible: 'show',
sortOrder: 'REVERSE_CHRONOLOGICAL',
};
export default function moderation (state = initialState, action) {
switch (action.type) {
case actions.MODERATION_CLEAR_STATE:
return initialState;
return {
...initialState,
shortcutsNoteVisible: state.shortcutsNoteVisible,
};
case actions.TOGGLE_MODAL:
return {
...state,
@@ -5,17 +5,24 @@ import {Icon} from 'coral-ui';
import t from 'coral-framework/services/i18n';
const refreshIntervalSeconds = 60 * 5;
// TODO: refactor out storage code into redux.
class CountdownTimer extends React.Component {
static contextTypes = {
storage: PropTypes.object,
};
static propTypes = {
handleTimeout: PropTypes.func.isRequired
}
constructor (props) {
super(props);
constructor (props, context) {
super(props, context);
const {storage} = context;
try {
if (window.localStorage.getItem('coral:dashboardNote') === null) {
window.localStorage.setItem('coral:dashboardNote', 'show');
if (storage && storage.getItem('coral:dashboardNote') === null) {
storage.setItem('coral:dashboardNote', 'show');
}
} catch (e) {
@@ -24,7 +31,7 @@ class CountdownTimer extends React.Component {
this.state = {
secondsUntilRefresh: refreshIntervalSeconds,
dashboardNote: window.localStorage.getItem('coral:dashboardNote') || 'show'
dashboardNote: (storage && storage.getItem('coral:dashboardNote')) || 'show'
};
}
@@ -54,8 +61,11 @@ class CountdownTimer extends React.Component {
}
dismissNote = () => {
const {storage} = this.context;
try {
window.localStorage.setItem('coral:dashboardNote', 'hide');
if (storage) {
storage.setItem('coral:dashboardNote', 'hide');
}
} catch (e) {
// when setItem fails in Safari Private mode
@@ -64,7 +74,8 @@ class CountdownTimer extends React.Component {
}
render () {
const hideReloadNote = window.localStorage.getItem('coral:dashboardNote') === 'hide' ||
const {storage} = this.context;
const hideReloadNote = (storage && storage.getItem('coral:dashboardNote') === 'hide') ||
this.state.dashboardNote === 'hide'; // for Safari Incognito
return (
<p
+16 -10
View File
@@ -1,7 +1,6 @@
import jwtDecode from 'jwt-decode';
import bowser from 'bowser';
import * as actions from '../constants/auth';
import * as Storage from 'coral-framework/helpers/storage';
import {notify} from 'coral-framework/actions/notification';
import t from 'coral-framework/services/i18n';
@@ -108,9 +107,11 @@ const signInFailure = (error) => ({
// AUTH TOKEN
//==============================================================================
export const handleAuthToken = (token) => (dispatch) => {
Storage.setItem('exp', jwtDecode(token).exp);
Storage.setItem('token', token);
export const handleAuthToken = (token) => (dispatch, _, {storage}) => {
if (storage) {
storage.setItem('exp', jwtDecode(token).exp);
storage.setItem('token', token);
}
dispatch({type: 'HANDLE_AUTH_TOKEN'});
};
@@ -272,9 +273,12 @@ export const fetchForgotPassword = (email) => (dispatch, getState, {rest}) => {
// LOGOUT
//==============================================================================
export const logout = () => async (dispatch, _, {rest, client, pym}) => {
export const logout = () => async (dispatch, _, {rest, client, pym, storage}) => {
await rest('/auth', {method: 'DELETE'});
Storage.removeItem('token');
if (storage) {
storage.removeItem('token');
}
// Reset the websocket.
client.resetWebsocket();
@@ -296,12 +300,14 @@ const checkLoginSuccess = (user, isAdmin) => ({
isAdmin
});
export const checkLogin = () => (dispatch, _, {rest, client, pym}) => {
export const checkLogin = () => (dispatch, _, {rest, client, pym, storage}) => {
dispatch(checkLoginRequest());
rest('/auth')
.then((result) => {
if (!result.user) {
Storage.removeItem('token');
if (storage) {
storage.removeItem('token');
}
throw new Error('Not logged in');
}
@@ -318,10 +324,10 @@ export const checkLogin = () => (dispatch, _, {rest, client, pym}) => {
})
.catch((error) => {
console.error(error);
if (error.status && error.status === 401) {
if (error.status && error.status === 401 && storage) {
// Unauthorized.
Storage.removeItem('token');
storage.removeItem('token');
}
const errorMessage = error.translation_key ? t(`error.${error.translation_key}`) : error.toString();
dispatch(checkLoginFailure(errorMessage));
@@ -11,6 +11,7 @@ class TalkProvider extends React.Component {
rest: this.props.rest,
graphqlRegistry: this.props.graphqlRegistry,
notification: this.props.notification,
storage: this.props.storage,
};
}
@@ -31,6 +32,7 @@ TalkProvider.childContextTypes = {
rest: PropTypes.func,
graphqlRegistry: PropTypes.object,
notification: PropTypes.object,
storage: PropTypes.object,
};
export default TalkProvider;
-92
View File
@@ -1,92 +0,0 @@
let available, error;
function storageAvailable(type) {
let storage = window[type], x = '__storage_test__';
try {
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
error = e;
return (
e instanceof DOMException &&
// everything except Firefox
(e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// acknowledge QuotaExceededError only if there's something already stored
storage.length !== 0
);
}
}
function lazyCheckStorage() {
if (typeof available === 'undefined') {
available = storageAvailable('localStorage');
}
}
export function getItem(item = '') {
lazyCheckStorage();
if (available) {
return localStorage.getItem(item);
} else {
console.error(
`Cannot get from localStorage. localStorage is not available. ${error}`
);
}
}
export function setItem(item = '', value) {
lazyCheckStorage();
if (available) {
return localStorage.setItem(item, value);
} else {
console.error(
`Cannot set localStorage. localStorage is not available. ${error}`
);
}
}
export function removeItem(item = '') {
lazyCheckStorage();
if (available) {
return localStorage.removeItem(item);
} else {
console.error(
`Cannot remove item from localStorage. localStorage is not available. ${error}`
);
}
}
export function clear() {
lazyCheckStorage();
if (available) {
return localStorage.clear();
} else {
console.error(
`Cannot clear localStorage. localStorage is not available. ${error}`
);
}
}
// window.addEventListener('storage', function(e) {
// const msg = `${e.key} " was changed in page ${e.url} from ${e.oldValue} to ${e.newValue}`;
// console.log(msg);
// });
+7 -5
View File
@@ -7,12 +7,12 @@ import {createRestClient} from './rest';
import thunk from 'redux-thunk';
import {loadTranslations} from './i18n';
import bowser from 'bowser';
import * as Storage from '../helpers/storage';
import {BASE_PATH} from 'coral-framework/constants/url';
import {createPluginsService} from './plugins';
import {createNotificationService} from './notification';
import {createGraphQLRegistry} from './graphqlRegistry';
import globalFragments from 'coral-framework/graphql/fragments';
import {createStorage} from 'coral-framework/services/storage';
/**
* getAuthToken returns the active auth token or null
@@ -20,7 +20,7 @@ import globalFragments from 'coral-framework/graphql/fragments';
* browsers that don't allow us to use cross domain iframe local storage.
* @return {string|null}
*/
const getAuthToken = (store) => {
const getAuthToken = (store, storage) => {
let state = store.getState();
if (state.config.auth_token) {
@@ -28,10 +28,10 @@ const getAuthToken = (store) => {
// if an auth_token exists in config, use it.
return state.config.auth_token;
} else if (!bowser.safari && !bowser.ios) {
} else if (!bowser.safari && !bowser.ios && storage) {
// Use local storage auth tokens where there's a stable api.
return Storage.getItem('token');
return storage.getItem('token');
}
return null;
@@ -40,6 +40,7 @@ const getAuthToken = (store) => {
export function createContext({reducers = {}, pluginsConfig = [], graphqlExtension = {}, notification}) {
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
const eventEmitter = new EventEmitter({wildcard: true});
const storage = createStorage();
let store = null;
const token = () => {
@@ -48,7 +49,7 @@ export function createContext({reducers = {}, pluginsConfig = [], graphqlExtensi
// NOTE: THIS IS ONLY EVER EVALUATED ONCE, IN ORDER TO SEND A DIFFERNT
// TOKEN YOU MUST DISCONNECT AND RECONNECT THE WEBSOCKET CLIENT.
return getAuthToken(store);
return getAuthToken(store, storage);
};
const rest = createRestClient({
uri: `${BASE_PATH}api/v1`,
@@ -74,6 +75,7 @@ export function createContext({reducers = {}, pluginsConfig = [], graphqlExtensi
rest,
graphqlRegistry,
notification,
storage,
};
// Load framework fragments.
@@ -0,0 +1,39 @@
function getStorage(type) {
let storage = window[type], x = '__storage_test__';
try {
storage.setItem(x, x);
storage.removeItem(x);
} catch (e) {
const ignore = (
e instanceof DOMException &&
// everything except Firefox
(e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// acknowledge QuotaExceededError only if there's something already stored
storage.length !== 0
);
if (!ignore) {
console.warning(e); // eslint-disable-line
return null;
}
}
return storage;
}
export function createStorage() {
return getStorage('localStorage');
}