Loading static/js/snakeoil.mjs +1 −1 Changes for static/js/snakeoil.mjs: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ export function getHandInToken() { * * Congratulations, you've found the security issue :) * The token for hand-in is just the Base64 encoded current state * (which we will later decode to check all exercises are completed), which * (which we will later decode to check all exercises are solved), which * is of course easily forged. Even if we signed this somehow, it * could still be forged, because everything is done client-side * and you have full control over the client. Loading static/js/state.mjs +19 −19 Changes for static/js/state.mjs: 19 added lines, 19 removed lines. Original line number Diff line number Diff line Loading @@ -28,8 +28,8 @@ export const currentState = { /** * Get the index of the exercise the user is currently working on */ get exercise() { const exerciseIndex = localStorage.getItem("exercise") get exerciseIndex() { const exerciseIndex = localStorage.getItem("exerciseIndex") if(exerciseIndex === null) { return null } else { Loading @@ -38,10 +38,10 @@ export const currentState = { }, /** * Set the number of the exercise the user is currently working on. * Set the index of the exercise the user is currently working on. * 0 <= index < exercises.length */ set exercise(index) { set exerciseIndex(index) { if(typeof index !== "number") { throw new Error("index must be a number") } Loading @@ -50,40 +50,40 @@ export const currentState = { throw new Error(`Exercise index must be >= 0 and < exercises.length (${exercises.length})`) } localStorage.setItem("exercise", index) localStorage.setItem("exerciseIndex", index) }, /** * Get a list of indices of all completed exercises * Get a list of indices of all solved exercises */ get completedExercises() { return JSON.parse(localStorage.getItem("completedExercises")) || [] get solvedExercises() { return JSON.parse(localStorage.getItem("solvedExercises")) || [] }, /** * Private setter for the list of all completed exercises * @param {Array} indices Array of indicies of exercises that are completed * Private setter for the list of all solved exercises * @param {Array} indices Array of indicies of exercises that are solved */ _setCompletedExercises(indices) { localStorage.setItem("completedExercises", JSON.stringify(indices)) _setSolvedExercises(indices) { localStorage.setItem("solvedExercises", JSON.stringify(indices)) }, /** * Mark the given exercise as completed * @param {Number} exerciseIndex The index of the exercise to be marked as complete * Mark the given exercise as solved * @param {Number} exerciseIndex The index of the exercise to be marked as solved */ complete(exerciseIndex) { if(this.completedExercises.includes(exerciseIndex)) { throw new Error(`Can't complete the same exercise (${exerciseIndex}) twice`) markAsSolved(exerciseIndex) { if(this.solvedExercises.includes(exerciseIndex)) { throw new Error(`Can't solve the same exercise (${exerciseIndex}) twice`) } if(exerciseIndex < 0 || exerciseIndex > exercises.length) { throw new Error(`Exercise index must be >= 0 and < exercises.length (${exercises.length})`) } const newIndices = this.completedExercises const newIndices = this.solvedExercises newIndices.push(exerciseIndex) this._setCompletedExercises(newIndices) this._setSolvedExercises(newIndices) }, /** Loading static/js/yalikejazz.mjs +12 −12 Changes for static/js/yalikejazz.mjs: 12 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -3,16 +3,16 @@ import { exercises } from "./exercises.mjs" import { getHandInToken } from "./snakeoil.mjs" import { createExerciseCard, displayAsSolved, displayAsNonCurrent, displayAsCurrent } from "./exercises.cards.mjs" function findNextIncompleteExerciseIndex(currentExerciseIndex) { if(currentState.completedExercises.length == exercises.length) { function findNextUnsolvedExerciseIndex(currentExerciseIndex) { if(currentState.solvedExercises.length == exercises.length) { return null } else { let nextIncompleteExerciseIndex = currentExerciseIndex while(currentState.completedExercises.includes(nextIncompleteExerciseIndex)) { nextIncompleteExerciseIndex = (nextIncompleteExerciseIndex+1) % exercises.length let nextUnsolvedExerciseIndex = currentExerciseIndex while(currentState.solvedExercises.includes(nextUnsolvedExerciseIndex)) { nextUnsolvedExerciseIndex = (nextUnsolvedExerciseIndex+1) % exercises.length } return nextIncompleteExerciseIndex return nextUnsolvedExerciseIndex } } Loading @@ -24,13 +24,13 @@ function findNextIncompleteExerciseIndex(currentExerciseIndex) { */ function createAndInitExerciseCard(exercise, exerciseIndex) { const cardEl = createExerciseCard(exercise, exerciseIndex, () => { if(!currentState.completedExercises.includes(exerciseIndex) && currentState.exercise !== exerciseIndex) { if(!currentState.solvedExercises.includes(exerciseIndex) && currentState.exerciseIndex !== exerciseIndex) { // Only skip to exercises that are not solved and aren't itself startExercise(exerciseIndex) } }) if(currentState.completedExercises.includes(exerciseIndex)) { if(currentState.solvedExercises.includes(exerciseIndex)) { displayAsSolved(cardEl) } Loading @@ -53,15 +53,15 @@ function startExercise(exerciseIndex) { const cardEl = document.querySelector(`.exercise[data-exercise-index='${exerciseIndex}']`) const exercise = exercises[exerciseIndex] currentState.exercise = exerciseIndex currentState.exerciseIndex = exerciseIndex exercise.onDescribed(description => cardEl.querySelector(".description").textContent = description) exercise.onVerified( success => { if(success) { currentState.complete(exerciseIndex) currentState.markAsSolved(exerciseIndex) displayAsSolved(cardEl) const nextExerciseIndex = findNextIncompleteExerciseIndex(exerciseIndex) const nextExerciseIndex = findNextUnsolvedExerciseIndex(exerciseIndex) if(nextExerciseIndex === null) { alert("You successfully completed the pool test! You can now hand in :)") Loading Loading @@ -118,7 +118,7 @@ export function main() { initActionLinks() // If there is no current exercise, start with the first one. startExercise(currentState.exercise || 0) startExercise(currentState.exerciseIndex || 0) } window.onload = main Loading
static/js/snakeoil.mjs +1 −1 Changes for static/js/snakeoil.mjs: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ export function getHandInToken() { * * Congratulations, you've found the security issue :) * The token for hand-in is just the Base64 encoded current state * (which we will later decode to check all exercises are completed), which * (which we will later decode to check all exercises are solved), which * is of course easily forged. Even if we signed this somehow, it * could still be forged, because everything is done client-side * and you have full control over the client. Loading
static/js/state.mjs +19 −19 Changes for static/js/state.mjs: 19 added lines, 19 removed lines. Original line number Diff line number Diff line Loading @@ -28,8 +28,8 @@ export const currentState = { /** * Get the index of the exercise the user is currently working on */ get exercise() { const exerciseIndex = localStorage.getItem("exercise") get exerciseIndex() { const exerciseIndex = localStorage.getItem("exerciseIndex") if(exerciseIndex === null) { return null } else { Loading @@ -38,10 +38,10 @@ export const currentState = { }, /** * Set the number of the exercise the user is currently working on. * Set the index of the exercise the user is currently working on. * 0 <= index < exercises.length */ set exercise(index) { set exerciseIndex(index) { if(typeof index !== "number") { throw new Error("index must be a number") } Loading @@ -50,40 +50,40 @@ export const currentState = { throw new Error(`Exercise index must be >= 0 and < exercises.length (${exercises.length})`) } localStorage.setItem("exercise", index) localStorage.setItem("exerciseIndex", index) }, /** * Get a list of indices of all completed exercises * Get a list of indices of all solved exercises */ get completedExercises() { return JSON.parse(localStorage.getItem("completedExercises")) || [] get solvedExercises() { return JSON.parse(localStorage.getItem("solvedExercises")) || [] }, /** * Private setter for the list of all completed exercises * @param {Array} indices Array of indicies of exercises that are completed * Private setter for the list of all solved exercises * @param {Array} indices Array of indicies of exercises that are solved */ _setCompletedExercises(indices) { localStorage.setItem("completedExercises", JSON.stringify(indices)) _setSolvedExercises(indices) { localStorage.setItem("solvedExercises", JSON.stringify(indices)) }, /** * Mark the given exercise as completed * @param {Number} exerciseIndex The index of the exercise to be marked as complete * Mark the given exercise as solved * @param {Number} exerciseIndex The index of the exercise to be marked as solved */ complete(exerciseIndex) { if(this.completedExercises.includes(exerciseIndex)) { throw new Error(`Can't complete the same exercise (${exerciseIndex}) twice`) markAsSolved(exerciseIndex) { if(this.solvedExercises.includes(exerciseIndex)) { throw new Error(`Can't solve the same exercise (${exerciseIndex}) twice`) } if(exerciseIndex < 0 || exerciseIndex > exercises.length) { throw new Error(`Exercise index must be >= 0 and < exercises.length (${exercises.length})`) } const newIndices = this.completedExercises const newIndices = this.solvedExercises newIndices.push(exerciseIndex) this._setCompletedExercises(newIndices) this._setSolvedExercises(newIndices) }, /** Loading
static/js/yalikejazz.mjs +12 −12 Changes for static/js/yalikejazz.mjs: 12 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -3,16 +3,16 @@ import { exercises } from "./exercises.mjs" import { getHandInToken } from "./snakeoil.mjs" import { createExerciseCard, displayAsSolved, displayAsNonCurrent, displayAsCurrent } from "./exercises.cards.mjs" function findNextIncompleteExerciseIndex(currentExerciseIndex) { if(currentState.completedExercises.length == exercises.length) { function findNextUnsolvedExerciseIndex(currentExerciseIndex) { if(currentState.solvedExercises.length == exercises.length) { return null } else { let nextIncompleteExerciseIndex = currentExerciseIndex while(currentState.completedExercises.includes(nextIncompleteExerciseIndex)) { nextIncompleteExerciseIndex = (nextIncompleteExerciseIndex+1) % exercises.length let nextUnsolvedExerciseIndex = currentExerciseIndex while(currentState.solvedExercises.includes(nextUnsolvedExerciseIndex)) { nextUnsolvedExerciseIndex = (nextUnsolvedExerciseIndex+1) % exercises.length } return nextIncompleteExerciseIndex return nextUnsolvedExerciseIndex } } Loading @@ -24,13 +24,13 @@ function findNextIncompleteExerciseIndex(currentExerciseIndex) { */ function createAndInitExerciseCard(exercise, exerciseIndex) { const cardEl = createExerciseCard(exercise, exerciseIndex, () => { if(!currentState.completedExercises.includes(exerciseIndex) && currentState.exercise !== exerciseIndex) { if(!currentState.solvedExercises.includes(exerciseIndex) && currentState.exerciseIndex !== exerciseIndex) { // Only skip to exercises that are not solved and aren't itself startExercise(exerciseIndex) } }) if(currentState.completedExercises.includes(exerciseIndex)) { if(currentState.solvedExercises.includes(exerciseIndex)) { displayAsSolved(cardEl) } Loading @@ -53,15 +53,15 @@ function startExercise(exerciseIndex) { const cardEl = document.querySelector(`.exercise[data-exercise-index='${exerciseIndex}']`) const exercise = exercises[exerciseIndex] currentState.exercise = exerciseIndex currentState.exerciseIndex = exerciseIndex exercise.onDescribed(description => cardEl.querySelector(".description").textContent = description) exercise.onVerified( success => { if(success) { currentState.complete(exerciseIndex) currentState.markAsSolved(exerciseIndex) displayAsSolved(cardEl) const nextExerciseIndex = findNextIncompleteExerciseIndex(exerciseIndex) const nextExerciseIndex = findNextUnsolvedExerciseIndex(exerciseIndex) if(nextExerciseIndex === null) { alert("You successfully completed the pool test! You can now hand in :)") Loading Loading @@ -118,7 +118,7 @@ export function main() { initActionLinks() // If there is no current exercise, start with the first one. startExercise(currentState.exercise || 0) startExercise(currentState.exerciseIndex || 0) } window.onload = main