Skip to content

Commit a267837

Browse files
andrewncatarak
authored andcommitted
Persists Redux store to/from sessionStorage (#334)
* Persists Redux store when reloading app for login * Disable confirmation box when leaving page for login * Removes extra console.warn * Sets serveSecure: true for new projects if served over HTTPS * Clears persisted state on IDEView load Because when a sketch is created on HTTPS and then the user logs in the page won't be reloaded * Appends ?source=<protocol> to URL to track return protocol
1 parent a4a1a36 commit a267837

File tree

10 files changed

+110
-14
lines changed

10 files changed

+110
-14
lines changed

client/components/forceProtocol.jsx

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { PropTypes } from 'react';
2+
import { format, parse } from 'url';
23

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

1516
componentDidMount() {
16-
this.redirectToProtocol(targetProtocol);
17+
this.redirectToProtocol(targetProtocol, { appendSource: true });
1718
}
1819

1920
componentWillUnmount() {
2021
if (sourceProtocol != null) {
21-
this.redirectToProtocol(sourceProtocol);
22+
this.redirectToProtocol(sourceProtocol, { appendSource: false });
2223
}
2324
}
2425

25-
redirectToProtocol(protocol) {
26-
const currentProtocol = window.location.protocol;
26+
redirectToProtocol(protocol, { appendSource }) {
27+
const currentProtocol = parse(window.location.href).protocol;
2728

2829
if (protocol !== currentProtocol) {
2930
if (disable === true) {
3031
console.info(`forceProtocol: would have redirected from "${currentProtocol}" to "${protocol}"`);
3132
} else {
32-
window.location = window.location.href.replace(currentProtocol, protocol);
33+
const url = parse(window.location.href, true /* parse query string */);
34+
url.protocol = protocol;
35+
if (appendSource === true) {
36+
url.query.source = currentProtocol;
37+
}
38+
window.location = format(url);
3339
}
3440
}
3541
}
@@ -40,5 +46,25 @@ const forceProtocol = ({ targetProtocol = 'https:', sourceProtocol, disable = fa
4046
}
4147
);
4248

49+
const protocols = {
50+
http: 'http:',
51+
https: 'https:',
52+
};
53+
54+
const findSourceProtocol = (state, location) => {
55+
if (/source=https/.test(window.location.search)) {
56+
return protocols.https;
57+
} else if (/source=http/.test(window.location.search)) {
58+
return protocols.http;
59+
} else if (state.project.serveSecure === true) {
60+
return protocols.https;
61+
}
62+
63+
return protocols.http;
64+
};
4365

4466
export default forceProtocol;
67+
export {
68+
findSourceProtocol,
69+
protocols,
70+
};

client/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,6 @@ export const RESET_PROJECT_SAVED_TIME = 'RESET_PROJECT_SAVED_TIME';
110110
export const SET_PREVIOUS_PATH = 'SET_PREVIOUS_PATH';
111111
export const SHOW_ERROR_MODAL = 'SHOW_ERROR_MODAL';
112112
export const HIDE_ERROR_MODAL = 'HIDE_ERROR_MODAL';
113+
114+
export const PERSIST_STATE = 'PERSIST_STATE';
115+
export const CLEAR_PERSISTED_STATE = 'CLEAR_PERSISTED_STATE';

client/modules/IDE/actions/project.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { setUnsavedChanges,
88
justOpenedProject,
99
resetJustOpenedProject,
1010
showErrorModal } from './ide';
11+
import { clearState, saveState } from '../../../persistState';
1112

1213
const ROOT_URL = process.env.API_URL;
1314

@@ -42,6 +43,25 @@ export function getProject(id) {
4243
};
4344
}
4445

46+
export function persistState() {
47+
return (dispatch, getState) => {
48+
dispatch({
49+
type: ActionTypes.PERSIST_STATE,
50+
});
51+
const state = getState();
52+
saveState(state);
53+
};
54+
}
55+
56+
export function clearPersistedState() {
57+
return (dispatch) => {
58+
dispatch({
59+
type: ActionTypes.CLEAR_PERSISTED_STATE,
60+
});
61+
clearState();
62+
};
63+
}
64+
4565
export function saveProject(autosave = false) {
4666
return (dispatch, getState) => {
4767
const state = getState();

client/modules/IDE/pages/IDEView.jsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ class IDEView extends React.Component {
4040
}
4141

4242
componentDidMount() {
43+
// If page doesn't reload after Sign In then we need
44+
// to force cleared state to be cleared
45+
this.props.clearPersistedState();
46+
4347
this.props.stopSketch();
4448
if (this.props.params.project_id) {
4549
const id = this.props.params.project_id;
@@ -170,8 +174,12 @@ class IDEView extends React.Component {
170174
warnIfUnsavedChanges(route) { // eslint-disable-line
171175
if (route && (route.action === 'PUSH' && (route.pathname === '/login' || route.pathname === '/signup'))) {
172176
// don't warn
177+
this.props.persistState();
178+
window.onbeforeunload = null;
173179
} else if (route && (this.props.location.pathname === '/login' || this.props.location.pathname === '/signup')) {
174180
// don't warn
181+
this.props.persistState();
182+
window.onbeforeunload = null;
175183
} else if (this.props.ide.unsavedChanges) {
176184
if (!window.confirm('Are you sure you want to leave this page? You have unsaved changes.')) {
177185
return false;
@@ -590,7 +598,9 @@ IDEView.propTypes = {
590598
})).isRequired,
591599
clearConsole: PropTypes.func.isRequired,
592600
showErrorModal: PropTypes.func.isRequired,
593-
hideErrorModal: PropTypes.func.isRequired
601+
hideErrorModal: PropTypes.func.isRequired,
602+
clearPersistedState: PropTypes.func.isRequired,
603+
persistState: PropTypes.func.isRequired
594604
};
595605

596606
function mapStateToProps(state) {

client/modules/IDE/reducers/project.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import generate from 'project-name-generator';
22
import * as ActionTypes from '../../../constants';
3+
import isSecurePage from '../../../utils/isSecurePage';
34

45
const initialState = () => {
56
const generatedString = generate({ words: 2 }).spaced;
67
const generatedName = generatedString.charAt(0).toUpperCase() + generatedString.slice(1);
78
return {
89
name: generatedName,
9-
serveSecure: false,
10+
serveSecure: isSecurePage(),
1011
};
1112
};
1213

client/persistState.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
Saves and loads a snapshot of the Redux store
3+
state to session storage
4+
*/
5+
const key = 'p5js-editor';
6+
const storage = sessionStorage;
7+
8+
export const saveState = (state) => {
9+
try {
10+
storage.setItem(key, JSON.stringify(state));
11+
} catch (error) {
12+
console.warn('Unable to persist state to storage:', error);
13+
}
14+
};
15+
16+
export const loadState = () => {
17+
try {
18+
return JSON.parse(storage.getItem(key));
19+
} catch (error) {
20+
console.warn('Failed to retrieve initialize state from storage:', error);
21+
return null;
22+
}
23+
};
24+
25+
export const clearState = () => {
26+
storage.removeItem(key);
27+
};

client/routes.jsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Route, IndexRoute } from 'react-router';
22
import React from 'react';
3-
import forceProtocol from './components/forceProtocol';
3+
import forceProtocol, { protocols, findSourceProtocol } from './components/forceProtocol';
44
import App from './modules/App/App';
55
import IDEView from './modules/IDE/pages/IDEView';
66
import FullView from './modules/IDE/pages/FullView';
@@ -17,13 +17,11 @@ const checkAuth = (store) => {
1717
};
1818

1919
const routes = (store) => {
20-
const sourceProtocol = store.getState().project.serveSecure === true ?
21-
'https:' :
22-
'http:';
20+
const sourceProtocol = findSourceProtocol(store.getState());
2321

2422
// If the flag is false, we stay on HTTP
2523
const forceToHttps = forceProtocol({
26-
targetProtocol: 'https:',
24+
targetProtocol: protocols.https,
2725
sourceProtocol,
2826
// prints debugging but does not reload page
2927
disable: process.env.FORCE_TO_HTTPS === false,

client/store.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { createStore, applyMiddleware, compose } from 'redux';
22
import thunk from 'redux-thunk';
33
import DevTools from './modules/App/components/DevTools';
44
import rootReducer from './reducers';
5+
import { clearState, loadState } from './persistState';
56

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

17+
const savedState = loadState();
18+
clearState();
19+
1620
const store = createStore(
1721
rootReducer,
18-
initialState,
22+
savedState != null ? savedState : initialState,
1923
compose(...enhancers)
2024
);
2125

client/utils/isSecurePage.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
const isSecurePage = () => (
3+
window.location.protocol === 'https:'
4+
);
5+
6+
export default isSecurePage;

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"s3-policy": "^0.2.0",
113113
"shortid": "^2.2.6",
114114
"srcdoc-polyfill": "^0.2.0",
115+
"url": "^0.11.0",
115116
"webpack": "^1.14.0",
116117
"webpack-dev-middleware": "^1.6.1",
117118
"webpack-hot-middleware": "^2.10.0",

0 commit comments

Comments
 (0)