From f9cf212fe72070623c6146a836c4f0c2c57dde4a Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Tue, 16 Aug 2016 15:59:23 -0700 Subject: [PATCH] Feature(code-uri): Lock untrusted code from playing on first load --- client/sagas/code-storage-saga.js | 2 + client/sagas/execute-challenge-saga.js | 2 + client/sagas/frame-saga.js | 14 ++--- .../components/classic/Side-Panel.jsx | 22 +++++--- .../components/classic/Tool-Panel.jsx | 54 ++++++++++++++----- common/app/routes/challenges/redux/actions.js | 4 ++ common/app/routes/challenges/redux/reducer.js | 2 +- common/app/routes/challenges/redux/types.js | 1 + 8 files changed, 75 insertions(+), 26 deletions(-) diff --git a/client/sagas/code-storage-saga.js b/client/sagas/code-storage-saga.js index ce77025c28..e1ad5d7b4f 100644 --- a/client/sagas/code-storage-saga.js +++ b/client/sagas/code-storage-saga.js @@ -53,6 +53,8 @@ function legacyToFile(code, files, key) { export function saveCodeSaga(actions, getState) { return actions ::ofType(types.saveCode) + // do not save challenge if code is locked + .filter(() => !getState().challengesApp.isCodeLocked) .map(() => { const { challengesApp: { id = '', files = {} } } = getState(); store.set(id, files); diff --git a/client/sagas/execute-challenge-saga.js b/client/sagas/execute-challenge-saga.js index 247502a910..3c28814faf 100644 --- a/client/sagas/execute-challenge-saga.js +++ b/client/sagas/execute-challenge-saga.js @@ -91,6 +91,8 @@ export default function executeChallengeSaga(action$, getState) { type === types.executeChallenge || type === types.updateMain )) + // if isCodeLockedTrue do not run challenges + .filter(() => !getState().challengesApp.isCodeLocked) .debounce(750) .flatMapLatest(({ type }) => { const state = getState(); diff --git a/client/sagas/frame-saga.js b/client/sagas/frame-saga.js index c9acf2f0ae..0aac0dd741 100644 --- a/client/sagas/frame-saga.js +++ b/client/sagas/frame-saga.js @@ -2,6 +2,7 @@ import Rx, { Observable, Subject } from 'rx'; /* eslint-disable import/no-unresolved */ import loopProtect from 'loop-protect'; /* eslint-enable import/no-unresolved */ +import { ofType } from '../../common/utils/get-actions-of-type'; import types from '../../common/app/routes/challenges/redux/types'; import { updateOutput, @@ -89,12 +90,13 @@ export default function frameSaga(actions$, getState, { window, document }) { const proxyLogger$ = new Subject(); const runTests$ = window.__common[testId + 'Ready$'] = new Subject(); - const result$ = actions$ - .filter(({ type }) => ( - type === types.frameMain || - type === types.frameTests || - type === types.frameOutput - )) + const result$ = actions$::ofType( + types.frameMain, + types.frameTests, + types.frameOutput + ) + // if isCodeLocked is true do not frame user code + .filter(() => !getState().challengesApp.isCodeLocked) .map(action => { if (action.type === types.frameMain) { return frameMain(action.payload, document, proxyLogger$); diff --git a/common/app/routes/challenges/components/classic/Side-Panel.jsx b/common/app/routes/challenges/components/classic/Side-Panel.jsx index 4175f9c122..3c45c6903b 100644 --- a/common/app/routes/challenges/components/classic/Side-Panel.jsx +++ b/common/app/routes/challenges/components/classic/Side-Panel.jsx @@ -12,7 +12,8 @@ import { challengeSelector } from '../../redux/selectors'; import { openBugModal, updateHint, - executeChallenge + executeChallenge, + unlockUntrustedCode } from '../../redux/actions'; import { makeToast } from '../../../../toasts/redux/actions'; import { toggleHelpChat } from '../../../../redux/actions'; @@ -22,7 +23,8 @@ const bindableActions = { executeChallenge, updateHint, toggleHelpChat, - openBugModal + openBugModal, + unlockUntrustedCode }; const mapStateToProps = createSelector( challengeSelector, @@ -31,20 +33,23 @@ const mapStateToProps = createSelector( state => state.challengesApp.tests, state => state.challengesApp.output, state => state.challengesApp.hintIndex, + state => state.challengesApp.isCodeLocked, ( { challenge: { title, description, hints = [] } = {} }, windowHeight, navHeight, tests, output, - hintIndex + hintIndex, + isCodeLocked ) => ({ title, description, height: windowHeight - navHeight - 20, tests, output, - hint: hints[hintIndex] + hint: hints[hintIndex], + isCodeLocked }) ); @@ -65,7 +70,8 @@ export class SidePanel extends PureComponent { updateHint: PropTypes.func, makeToast: PropTypes.func, toggleHelpChat: PropTypes.func, - openBugModal: PropTypes.func + openBugModal: PropTypes.func, + unlockUntrustedCode: PropTypes.func }; renderDescription(description = [ 'Happy Coding!' ], descriptionRegex) { @@ -106,7 +112,9 @@ export class SidePanel extends PureComponent { updateHint, makeToast, toggleHelpChat, - openBugModal + openBugModal, + isCodeLocked, + unlockUntrustedCode } = this.props; const style = {}; if (height) { @@ -135,9 +143,11 @@ export class SidePanel extends PureComponent { diff --git a/common/app/routes/challenges/components/classic/Tool-Panel.jsx b/common/app/routes/challenges/components/classic/Tool-Panel.jsx index adabc3e94a..44f09aa518 100644 --- a/common/app/routes/challenges/components/classic/Tool-Panel.jsx +++ b/common/app/routes/challenges/components/classic/Tool-Panel.jsx @@ -14,8 +14,10 @@ export default class ToolPanel extends PureComponent { executeChallenge: PropTypes.func, updateHint: PropTypes.func, hint: PropTypes.string, + isCodeLocked: PropTypes.bool, toggleHelpChat: PropTypes.func, - openBugModal: PropTypes.func + openBugModal: PropTypes.func, + unlockUntrustedCode: PropTypes.func.isRequired }; makeHint() { @@ -51,24 +53,50 @@ export default class ToolPanel extends PureComponent { ); } - render() { - const { - hint, - executeChallenge, - toggleHelpChat, - openBugModal - } = this.props; - return ( -
- { this.renderHint(hint, this.makeHint) } + renderExecute(isCodeLocked, executeChallenge, unlockUntrustedCode) { + if (isCodeLocked) { + return ( + ); + } + return ( + + ); + } + + render() { + const { + hint, + isCodeLocked, + executeChallenge, + toggleHelpChat, + openBugModal, + unlockUntrustedCode + } = this.props; + return ( +
+ { this.renderHint(hint, this.makeHint) } + { + this.renderExecute( + isCodeLocked, + executeChallenge, + unlockUntrustedCode + ) + }
null +); export const fetchChallenges = createAction(types.fetchChallenges); export const fetchChallengesCompleted = createAction( diff --git a/common/app/routes/challenges/redux/reducer.js b/common/app/routes/challenges/redux/reducer.js index 12248cc689..7c88d212d1 100644 --- a/common/app/routes/challenges/redux/reducer.js +++ b/common/app/routes/challenges/redux/reducer.js @@ -93,7 +93,7 @@ const mainReducer = handleActions( ...state, isCodeLocked: true }), - [types.unlockCode]: state => ({ + [types.unlockUntrustedCode]: state => ({ ...state, isCodeLocked: false }), diff --git a/common/app/routes/challenges/redux/types.js b/common/app/routes/challenges/redux/types.js index 15e4675b99..5601ff2935 100644 --- a/common/app/routes/challenges/redux/types.js +++ b/common/app/routes/challenges/redux/types.js @@ -18,6 +18,7 @@ export default createTypes([ 'resetUi', 'updateHint', 'lockUntrustedCode', + 'unlockUntrustedCode', // map 'updateFilter',