Skip to content

Persists Redux store to/from sessionStorage #334

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 20, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions client/components/forceProtocol.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { PropTypes } from 'react';
import { format, parse } from 'url';

/**
* A Higher Order Component that forces the protocol to change on mount
Expand All @@ -8,28 +9,33 @@ import React, { PropTypes } from 'react';
* disable: if true, the redirection will not happen but what should
* have happened will be logged to the console
*/
const forceProtocol = ({ targetProtocol = 'https:', sourceProtocol, disable = false }) => WrappedComponent => (
const forceProtocol = ({ targetProtocol = 'https', sourceProtocol, disable = false }) => WrappedComponent => (
class ForceProtocol extends React.Component {
static propTypes = {}

componentDidMount() {
this.redirectToProtocol(targetProtocol);
this.redirectToProtocol(targetProtocol, { appendSource: true });
}

componentWillUnmount() {
if (sourceProtocol != null) {
this.redirectToProtocol(sourceProtocol);
this.redirectToProtocol(sourceProtocol, { appendSource: false });
}
}

redirectToProtocol(protocol) {
const currentProtocol = window.location.protocol;
redirectToProtocol(protocol, { appendSource }) {
const currentProtocol = parse(window.location.href).protocol;

if (protocol !== currentProtocol) {
if (disable === true) {
console.info(`forceProtocol: would have redirected from "${currentProtocol}" to "${protocol}"`);
} else {
window.location = window.location.href.replace(currentProtocol, protocol);
const url = parse(window.location.href, true /* parse query string */);
url.protocol = protocol;
if (appendSource === true) {
url.query.source = currentProtocol;
}
window.location = format(url);
}
}
}
Expand All @@ -40,5 +46,25 @@ const forceProtocol = ({ targetProtocol = 'https:', sourceProtocol, disable = fa
}
);

const protocols = {
http: 'http:',
https: 'https:',
};

const findSourceProtocol = (state, location) => {
if (/source=https/.test(window.location.search)) {
return protocols.https;
} else if (/source=http/.test(window.location.search)) {
return protocols.http;
} else if (state.project.serveSecure === true) {
return protocols.https;
}

return protocols.http;
};

export default forceProtocol;
export {
findSourceProtocol,
protocols,
};
3 changes: 3 additions & 0 deletions client/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,6 @@ export const RESET_PROJECT_SAVED_TIME = 'RESET_PROJECT_SAVED_TIME';
export const SET_PREVIOUS_PATH = 'SET_PREVIOUS_PATH';
export const SHOW_ERROR_MODAL = 'SHOW_ERROR_MODAL';
export const HIDE_ERROR_MODAL = 'HIDE_ERROR_MODAL';

export const PERSIST_STATE = 'PERSIST_STATE';
export const CLEAR_PERSISTED_STATE = 'CLEAR_PERSISTED_STATE';
20 changes: 20 additions & 0 deletions client/modules/IDE/actions/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { setUnsavedChanges,
justOpenedProject,
resetJustOpenedProject,
showErrorModal } from './ide';
import { clearState, saveState } from '../../../persistState';

const ROOT_URL = process.env.API_URL;

Expand Down Expand Up @@ -41,6 +42,25 @@ export function getProject(id) {
};
}

export function persistState() {
return (dispatch, getState) => {
dispatch({
type: ActionTypes.PERSIST_STATE,
});
const state = getState();
saveState(state);
};
}

export function clearPersistedState() {
return (dispatch) => {
dispatch({
type: ActionTypes.CLEAR_PERSISTED_STATE,
});
clearState();
};
}

export function saveProject(autosave = false) {
return (dispatch, getState) => {
const state = getState();
Expand Down
12 changes: 11 additions & 1 deletion client/modules/IDE/pages/IDEView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ class IDEView extends React.Component {
}

componentDidMount() {
// If page doesn't reload after Sign In then we need
// to force cleared state to be cleared
this.props.clearPersistedState();

this.props.stopSketch();
if (this.props.params.project_id) {
const id = this.props.params.project_id;
Expand Down Expand Up @@ -170,8 +174,12 @@ class IDEView extends React.Component {
warnIfUnsavedChanges(route) { // eslint-disable-line
if (route && (route.action === 'PUSH' && (route.pathname === '/login' || route.pathname === '/signup'))) {
// don't warn
this.props.persistState();
window.onbeforeunload = null;
} else if (route && (this.props.location.pathname === '/login' || this.props.location.pathname === '/signup')) {
// don't warn
this.props.persistState();
window.onbeforeunload = null;
} else if (this.props.ide.unsavedChanges) {
if (!window.confirm('Are you sure you want to leave this page? You have unsaved changes.')) {
return false;
Expand Down Expand Up @@ -588,7 +596,9 @@ IDEView.propTypes = {
})).isRequired,
clearConsole: PropTypes.func.isRequired,
showErrorModal: PropTypes.func.isRequired,
hideErrorModal: PropTypes.func.isRequired
hideErrorModal: PropTypes.func.isRequired,
clearPersistedState: PropTypes.func.isRequired,
persistState: PropTypes.func.isRequired
};

function mapStateToProps(state) {
Expand Down
3 changes: 2 additions & 1 deletion client/modules/IDE/reducers/project.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import generate from 'project-name-generator';
import * as ActionTypes from '../../../constants';
import isSecurePage from '../../../utils/isSecurePage';

const initialState = () => {
const generatedString = generate({ words: 2 }).spaced;
const generatedName = generatedString.charAt(0).toUpperCase() + generatedString.slice(1);
return {
name: generatedName,
serveSecure: false,
serveSecure: isSecurePage(),
};
};

Expand Down
27 changes: 27 additions & 0 deletions client/persistState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Saves and loads a snapshot of the Redux store
state to session storage
*/
const key = 'p5js-editor';
const storage = sessionStorage;

export const saveState = (state) => {
try {
storage.setItem(key, JSON.stringify(state));
} catch (error) {
console.warn('Unable to persist state to storage:', error);
}
};

export const loadState = () => {
try {
return JSON.parse(storage.getItem(key));
} catch (error) {
console.warn('Failed to retrieve initialize state from storage:', error);
return null;
}
};

export const clearState = () => {
storage.removeItem(key);
};
8 changes: 3 additions & 5 deletions client/routes.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Route, IndexRoute } from 'react-router';
import React from 'react';
import forceProtocol from './components/forceProtocol';
import forceProtocol, { protocols, findSourceProtocol } from './components/forceProtocol';
import App from './modules/App/App';
import IDEView from './modules/IDE/pages/IDEView';
import FullView from './modules/IDE/pages/FullView';
Expand All @@ -17,13 +17,11 @@ const checkAuth = (store) => {
};

const routes = (store) => {
const sourceProtocol = store.getState().project.serveSecure === true ?
'https:' :
'http:';
const sourceProtocol = findSourceProtocol(store.getState());

// If the flag is false, we stay on HTTP
const forceToHttps = forceProtocol({
targetProtocol: 'https:',
targetProtocol: protocols.https,
sourceProtocol,
// prints debugging but does not reload page
disable: process.env.FORCE_TO_HTTPS === false,
Expand Down
6 changes: 5 additions & 1 deletion client/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import DevTools from './modules/App/components/DevTools';
import rootReducer from './reducers';
import { clearState, loadState } from './persistState';

export default function configureStore(initialState) {
const enhancers = [
Expand All @@ -13,9 +14,12 @@ export default function configureStore(initialState) {
enhancers.push(window.devToolsExtension ? window.devToolsExtension() : DevTools.instrument());
}

const savedState = loadState();
clearState();

const store = createStore(
rootReducer,
initialState,
savedState != null ? savedState : initialState,
compose(...enhancers)
);

Expand Down
6 changes: 6 additions & 0 deletions client/utils/isSecurePage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

const isSecurePage = () => (
window.location.protocol === 'https:'
);

export default isSecurePage;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"s3-policy": "^0.2.0",
"shortid": "^2.2.6",
"srcdoc-polyfill": "^0.2.0",
"url": "^0.11.0",
"webpack": "^1.14.0",
"webpack-dev-middleware": "^1.6.1",
"webpack-hot-middleware": "^2.10.0",
Expand Down