mirror of
https://github.com/wassname/talk.git
synced 2026-07-15 11:26:58 +08:00
Remove global dependency on Storage
This commit is contained in:
@@ -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'});
|
||||
};
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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');
|
||||
}
|
||||
Reference in New Issue
Block a user