Skip to content

HTTPS UI switch #335

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 7 commits into from
May 3, 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
46 changes: 26 additions & 20 deletions client/components/forceProtocol.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
import React, { PropTypes } from 'react';
import React from 'react';
import { format, parse } from 'url';

const findCurrentProtocol = () => (
parse(window.location.href).protocol
);

const redirectToProtocol = (protocol, { appendSource, disable = false } = {}) => {
const currentProtocol = findCurrentProtocol();

if (protocol !== currentProtocol) {
if (disable === true) {
console.info(`forceProtocol: would have redirected from "${currentProtocol}" to "${protocol}"`);
} else {
const url = parse(window.location.href, true /* parse query string */);
url.protocol = protocol;
if (appendSource === true) {
url.query.source = currentProtocol;
}
window.location = format(url);
}
}
};

/**
* A Higher Order Component that forces the protocol to change on mount
*
Expand All @@ -14,29 +35,12 @@ const forceProtocol = ({ targetProtocol = 'https', sourceProtocol, disable = fal
static propTypes = {}

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

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

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 {
const url = parse(window.location.href, true /* parse query string */);
url.protocol = protocol;
if (appendSource === true) {
url.query.source = currentProtocol;
}
window.location = format(url);
}
redirectToProtocol(sourceProtocol, { appendSource: false, disable });
}
}

Expand Down Expand Up @@ -65,6 +69,8 @@ const findSourceProtocol = (state, location) => {

export default forceProtocol;
export {
findCurrentProtocol,
findSourceProtocol,
redirectToProtocol,
protocols,
};
4 changes: 4 additions & 0 deletions client/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const AUTH_ERROR = 'AUTH_ERROR';
export const SETTINGS_UPDATED = 'SETTINGS_UPDATED';

export const SET_PROJECT_NAME = 'SET_PROJECT_NAME';
export const SET_SERVE_SECURE = 'SET_SERVE_SECURE';

export const PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS';
export const PROJECT_SAVE_FAIL = 'PROJECT_SAVE_FAIL';
Expand Down Expand Up @@ -113,3 +114,6 @@ export const HIDE_ERROR_MODAL = 'HIDE_ERROR_MODAL';

export const PERSIST_STATE = 'PERSIST_STATE';
export const CLEAR_PERSISTED_STATE = 'CLEAR_PERSISTED_STATE';

export const SHOW_HELP_MODAL = 'SHOW_HELP_MODAL';
export const HIDE_HELP_MODAL = 'HIDE_HELP_MODAL';
7 changes: 7 additions & 0 deletions client/images/help.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions client/modules/IDE/actions/ide.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,16 @@ export function hideErrorModal() {
type: ActionTypes.HIDE_ERROR_MODAL
};
}

export function showHelpModal(helpType) {
return {
type: ActionTypes.SHOW_HELP_MODAL,
helpType
};
}

export function hideHelpModal() {
return {
type: ActionTypes.HIDE_HELP_MODAL
};
}
96 changes: 61 additions & 35 deletions client/modules/IDE/actions/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@ import { setUnsavedChanges,
resetJustOpenedProject,
showErrorModal } from './ide';
import { clearState, saveState } from '../../../persistState';
import { redirectToProtocol, protocols } from '../../../components/forceProtocol';

const ROOT_URL = process.env.API_URL;

export function setProject(project) {
const targetProtocol = project.serveSecure === true ?
protocols.https :
protocols.http;

// This will not reload if on same protocol
redirectToProtocol(targetProtocol);

return {
type: ActionTypes.SET_PROJECT,
project,
Expand Down Expand Up @@ -66,12 +74,12 @@ export function saveProject(autosave = false) {
return (dispatch, getState) => {
const state = getState();
if (state.user.id && state.project.owner && state.project.owner.id !== state.user.id) {
return;
return Promise.reject();
}
const formParams = Object.assign({}, state.project);
formParams.files = [...state.files];
if (state.project.id) {
axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
return axios.put(`${ROOT_URL}/projects/${state.project.id}`, formParams, { withCredentials: true })
.then((response) => {
dispatch(setUnsavedChanges(false));
console.log(response.data);
Expand Down Expand Up @@ -103,41 +111,41 @@ export function saveProject(autosave = false) {
});
}
});
} else {
axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
.then((response) => {
dispatch(setUnsavedChanges(false));
dispatch(setProject(response.data));
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
dispatch({
type: ActionTypes.NEW_PROJECT,
project: response.data,
owner: response.data.user,
files: response.data.files
});
if (!autosave) {
if (state.preferences.autosave) {
dispatch(showToast(5500));
dispatch(setToastText('Project saved.'));
setTimeout(() => dispatch(setToastText('Autosave enabled.')), 1500);
dispatch(resetJustOpenedProject());
} else {
dispatch(showToast(1500));
dispatch(setToastText('Project saved.'));
}
}
})
.catch((response) => {
if (response.status === 403) {
dispatch(showErrorModal('staleSession'));
}

return axios.post(`${ROOT_URL}/projects`, formParams, { withCredentials: true })
.then((response) => {
dispatch(setUnsavedChanges(false));
dispatch(setProject(response.data));
browserHistory.push(`/${response.data.user.username}/sketches/${response.data.id}`);
dispatch({
type: ActionTypes.NEW_PROJECT,
project: response.data,
owner: response.data.user,
files: response.data.files
});
if (!autosave) {
if (state.preferences.autosave) {
dispatch(showToast(5500));
dispatch(setToastText('Project saved.'));
setTimeout(() => dispatch(setToastText('Autosave enabled.')), 1500);
dispatch(resetJustOpenedProject());
} else {
dispatch({
type: ActionTypes.PROJECT_SAVE_FAIL,
error: response.data
});
dispatch(showToast(1500));
dispatch(setToastText('Project saved.'));
}
});
}
}
})
.catch((response) => {
if (response.status === 403) {
dispatch(showErrorModal('staleSession'));
} else {
dispatch({
type: ActionTypes.PROJECT_SAVE_FAIL,
error: response.data
});
}
});
};
}

Expand Down Expand Up @@ -249,6 +257,24 @@ export function cloneProject() {
};
}

export function setServeSecure(serveSecure, { redirect = true } = {}) {
return (dispatch, getState) => {
dispatch({
type: ActionTypes.SET_SERVE_SECURE,
serveSecure
});

if (redirect === true) {
dispatch(saveProject(false /* autosave */))
.then(
() => redirectToProtocol(serveSecure === true ? protocols.https : protocols.http)
);
}

return null;
};
}

export function showEditProjectName() {
return {
type: ActionTypes.SHOW_EDIT_PROJECT_NAME
Expand Down
61 changes: 61 additions & 0 deletions client/modules/IDE/components/HelpModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { PropTypes } from 'react';
import InlineSVG from 'react-inlinesvg';

const exitUrl = require('../../../images/exit.svg');

const helpContent = {
serveSecure: {
title: 'Serve over HTTPS',
body: (
<div>
<p>Use the checkbox to choose whether this sketch should be loaded using HTTPS or HTTP.</p>
<p>You should choose HTTPS if you need to:</p>
<ul>
<li>access a webcam or microphone</li>
<li>access an API served over HTTPS</li>
</ul>
<p>Choose HTTP if you need to:</p>
<ul>
<li>access an API served over HTTP</li>
</ul>
</div>
)
}
};

const fallbackContent = {
title: 'No content for this topic',
body: null,
};

class HelpModal extends React.Component {
componentDidMount() {
this.shareModal.focus();
}
render() {
const content = helpContent[this.props.type] == null ?
fallbackContent :
helpContent[this.props.type];

return (
<section className="help-modal" ref={(element) => { this.shareModal = element; }} tabIndex="0">
<header className="help-modal__header">
<h2>{content.title}</h2>
<button className="about__exit-button" onClick={this.props.closeModal}>
<InlineSVG src={exitUrl} alt="Close Help Overlay" />
</button>
</header>
<div className="help-modal__section">
{content.body}
</div>
</section>
);
}
}

HelpModal.propTypes = {
type: PropTypes.string.isRequired,
closeModal: PropTypes.func.isRequired,
};

export default HelpModal;
30 changes: 29 additions & 1 deletion client/modules/IDE/components/Toolbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const logoUrl = require('../../../images/p5js-logo.svg');
const stopUrl = require('../../../images/stop.svg');
const preferencesUrl = require('../../../images/preferences.svg');
const editProjectNameUrl = require('../../../images/pencil.svg');
const helpUrl = require('../../../images/help.svg');

class Toolbar extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -102,6 +103,30 @@ class Toolbar extends React.Component {
Auto-refresh
</label>
</div>
{
this.props.currentUser == null ?
null :
<div className="toolbar__serve-secure">
<input
id="serve-secure"
type="checkbox"
checked={this.props.project.serveSecure || false}
onChange={(event) => {
this.props.setServeSecure(event.target.checked);
}}
/>
<label htmlFor="serve-secure" className="toolbar__serve-secure-label">
HTTPS
</label>
<button
className="toolbar__serve-secure-help"
onClick={() => this.props.showHelpModal('serveSecure')}
aria-label="help"
>
<InlineSVG src={helpUrl} alt="Help" />
</button>
</div>
}
<div className={nameContainerClass}>
<a
className="toolbar__project-name"
Expand Down Expand Up @@ -169,13 +194,16 @@ Toolbar.propTypes = {
project: PropTypes.shape({
name: PropTypes.string.isRequired,
isEditingName: PropTypes.bool,
id: PropTypes.string
id: PropTypes.string,
serveSecure: PropTypes.bool,
}).isRequired,
showEditProjectName: PropTypes.func.isRequired,
hideEditProjectName: PropTypes.func.isRequired,
showHelpModal: PropTypes.func.isRequired,
infiniteLoop: PropTypes.bool.isRequired,
autorefresh: PropTypes.bool.isRequired,
setAutorefresh: PropTypes.func.isRequired,
setServeSecure: PropTypes.func.isRequired,
startSketchAndRefresh: PropTypes.func.isRequired,
saveProject: PropTypes.func.isRequired,
currentUser: PropTypes.string,
Expand Down
Loading