Feature(code-uri): Lock untrusted code from playing on first load

This commit is contained in:
Berkeley Martinez
2016-08-16 15:59:23 -07:00
parent fb9c1001b0
commit f9cf212fe7
8 changed files with 75 additions and 26 deletions

View File

@ -53,6 +53,8 @@ function legacyToFile(code, files, key) {
export function saveCodeSaga(actions, getState) { export function saveCodeSaga(actions, getState) {
return actions return actions
::ofType(types.saveCode) ::ofType(types.saveCode)
// do not save challenge if code is locked
.filter(() => !getState().challengesApp.isCodeLocked)
.map(() => { .map(() => {
const { challengesApp: { id = '', files = {} } } = getState(); const { challengesApp: { id = '', files = {} } } = getState();
store.set(id, files); store.set(id, files);

View File

@ -91,6 +91,8 @@ export default function executeChallengeSaga(action$, getState) {
type === types.executeChallenge || type === types.executeChallenge ||
type === types.updateMain type === types.updateMain
)) ))
// if isCodeLockedTrue do not run challenges
.filter(() => !getState().challengesApp.isCodeLocked)
.debounce(750) .debounce(750)
.flatMapLatest(({ type }) => { .flatMapLatest(({ type }) => {
const state = getState(); const state = getState();

View File

@ -2,6 +2,7 @@ import Rx, { Observable, Subject } from 'rx';
/* eslint-disable import/no-unresolved */ /* eslint-disable import/no-unresolved */
import loopProtect from 'loop-protect'; import loopProtect from 'loop-protect';
/* eslint-enable import/no-unresolved */ /* eslint-enable import/no-unresolved */
import { ofType } from '../../common/utils/get-actions-of-type';
import types from '../../common/app/routes/challenges/redux/types'; import types from '../../common/app/routes/challenges/redux/types';
import { import {
updateOutput, updateOutput,
@ -89,12 +90,13 @@ export default function frameSaga(actions$, getState, { window, document }) {
const proxyLogger$ = new Subject(); const proxyLogger$ = new Subject();
const runTests$ = window.__common[testId + 'Ready$'] = const runTests$ = window.__common[testId + 'Ready$'] =
new Subject(); new Subject();
const result$ = actions$ const result$ = actions$::ofType(
.filter(({ type }) => ( types.frameMain,
type === types.frameMain || types.frameTests,
type === types.frameTests || types.frameOutput
type === types.frameOutput )
)) // if isCodeLocked is true do not frame user code
.filter(() => !getState().challengesApp.isCodeLocked)
.map(action => { .map(action => {
if (action.type === types.frameMain) { if (action.type === types.frameMain) {
return frameMain(action.payload, document, proxyLogger$); return frameMain(action.payload, document, proxyLogger$);

View File

@ -12,7 +12,8 @@ import { challengeSelector } from '../../redux/selectors';
import { import {
openBugModal, openBugModal,
updateHint, updateHint,
executeChallenge executeChallenge,
unlockUntrustedCode
} from '../../redux/actions'; } from '../../redux/actions';
import { makeToast } from '../../../../toasts/redux/actions'; import { makeToast } from '../../../../toasts/redux/actions';
import { toggleHelpChat } from '../../../../redux/actions'; import { toggleHelpChat } from '../../../../redux/actions';
@ -22,7 +23,8 @@ const bindableActions = {
executeChallenge, executeChallenge,
updateHint, updateHint,
toggleHelpChat, toggleHelpChat,
openBugModal openBugModal,
unlockUntrustedCode
}; };
const mapStateToProps = createSelector( const mapStateToProps = createSelector(
challengeSelector, challengeSelector,
@ -31,20 +33,23 @@ const mapStateToProps = createSelector(
state => state.challengesApp.tests, state => state.challengesApp.tests,
state => state.challengesApp.output, state => state.challengesApp.output,
state => state.challengesApp.hintIndex, state => state.challengesApp.hintIndex,
state => state.challengesApp.isCodeLocked,
( (
{ challenge: { title, description, hints = [] } = {} }, { challenge: { title, description, hints = [] } = {} },
windowHeight, windowHeight,
navHeight, navHeight,
tests, tests,
output, output,
hintIndex hintIndex,
isCodeLocked
) => ({ ) => ({
title, title,
description, description,
height: windowHeight - navHeight - 20, height: windowHeight - navHeight - 20,
tests, tests,
output, output,
hint: hints[hintIndex] hint: hints[hintIndex],
isCodeLocked
}) })
); );
@ -65,7 +70,8 @@ export class SidePanel extends PureComponent {
updateHint: PropTypes.func, updateHint: PropTypes.func,
makeToast: PropTypes.func, makeToast: PropTypes.func,
toggleHelpChat: PropTypes.func, toggleHelpChat: PropTypes.func,
openBugModal: PropTypes.func openBugModal: PropTypes.func,
unlockUntrustedCode: PropTypes.func
}; };
renderDescription(description = [ 'Happy Coding!' ], descriptionRegex) { renderDescription(description = [ 'Happy Coding!' ], descriptionRegex) {
@ -106,7 +112,9 @@ export class SidePanel extends PureComponent {
updateHint, updateHint,
makeToast, makeToast,
toggleHelpChat, toggleHelpChat,
openBugModal openBugModal,
isCodeLocked,
unlockUntrustedCode
} = this.props; } = this.props;
const style = {}; const style = {};
if (height) { if (height) {
@ -135,9 +143,11 @@ export class SidePanel extends PureComponent {
<ToolPanel <ToolPanel
executeChallenge={ executeChallenge } executeChallenge={ executeChallenge }
hint={ hint } hint={ hint }
isCodeLocked={ isCodeLocked }
makeToast={ makeToast } makeToast={ makeToast }
openBugModal={ openBugModal } openBugModal={ openBugModal }
toggleHelpChat={ toggleHelpChat } toggleHelpChat={ toggleHelpChat }
unlockUntrustedCode={ unlockUntrustedCode }
updateHint={ updateHint } updateHint={ updateHint }
/> />
<Output output={ output }/> <Output output={ output }/>

View File

@ -14,8 +14,10 @@ export default class ToolPanel extends PureComponent {
executeChallenge: PropTypes.func, executeChallenge: PropTypes.func,
updateHint: PropTypes.func, updateHint: PropTypes.func,
hint: PropTypes.string, hint: PropTypes.string,
isCodeLocked: PropTypes.bool,
toggleHelpChat: PropTypes.func, toggleHelpChat: PropTypes.func,
openBugModal: PropTypes.func openBugModal: PropTypes.func,
unlockUntrustedCode: PropTypes.func.isRequired
}; };
makeHint() { makeHint() {
@ -51,16 +53,20 @@ export default class ToolPanel extends PureComponent {
); );
} }
render() { renderExecute(isCodeLocked, executeChallenge, unlockUntrustedCode) {
const { if (isCodeLocked) {
hint, return (
executeChallenge, <Button
toggleHelpChat, block={ true }
openBugModal bsStyle='primary'
} = this.props; className='btn-big'
onClick={ unlockUntrustedCode }
>
Code Locked. Unlock?
</Button>
);
}
return ( return (
<div>
{ this.renderHint(hint, this.makeHint) }
<Button <Button
block={ true } block={ true }
bsStyle='primary' bsStyle='primary'
@ -69,6 +75,28 @@ export default class ToolPanel extends PureComponent {
> >
Run tests (ctrl + enter) Run tests (ctrl + enter)
</Button> </Button>
);
}
render() {
const {
hint,
isCodeLocked,
executeChallenge,
toggleHelpChat,
openBugModal,
unlockUntrustedCode
} = this.props;
return (
<div>
{ this.renderHint(hint, this.makeHint) }
{
this.renderExecute(
isCodeLocked,
executeChallenge,
unlockUntrustedCode
)
}
<div className='button-spacer' /> <div className='button-spacer' />
<ButtonGroup <ButtonGroup
className='input-group' className='input-group'

View File

@ -23,6 +23,10 @@ export const fetchChallengeCompleted = createAction(
export const resetUi = createAction(types.resetUi); export const resetUi = createAction(types.resetUi);
export const updateHint = createAction(types.updateHint); export const updateHint = createAction(types.updateHint);
export const lockUntrustedCode = createAction(types.lockUntrustedCode); export const lockUntrustedCode = createAction(types.lockUntrustedCode);
export const unlockUntrustedCode = createAction(
types.unlockUntrustedCode,
() => null
);
export const fetchChallenges = createAction(types.fetchChallenges); export const fetchChallenges = createAction(types.fetchChallenges);
export const fetchChallengesCompleted = createAction( export const fetchChallengesCompleted = createAction(

View File

@ -93,7 +93,7 @@ const mainReducer = handleActions(
...state, ...state,
isCodeLocked: true isCodeLocked: true
}), }),
[types.unlockCode]: state => ({ [types.unlockUntrustedCode]: state => ({
...state, ...state,
isCodeLocked: false isCodeLocked: false
}), }),

View File

@ -18,6 +18,7 @@ export default createTypes([
'resetUi', 'resetUi',
'updateHint', 'updateHint',
'lockUntrustedCode', 'lockUntrustedCode',
'unlockUntrustedCode',
// map // map
'updateFilter', 'updateFilter',