### Install and Run Monkeytype Locally Source: https://context7.com/monkeytypegame/monkeytype/llms.txt Clone the repository, install dependencies using pnpm, and start the development server for full-stack or individual backend/frontend hot-reloading. ```bash # Clone and install git clone https://github.com/monkeytypegame/monkeytype.git cd monkeytype pnpm install # Start full dev stack (frontend + backend hot-reload) pnpm dev # Backend only pnpm dev-be # Frontend only pnpm dev-fe # Run tests pnpm test # Build production artifacts pnpm build ``` -------------------------------- ### Install Project Dependencies Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Run this command in the project root to install all necessary dependencies for both frontend and backend if running manually. ```bash pnpm i ``` -------------------------------- ### Start Backend Database with Docker Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Starts the backend database services using Docker. This command should be run from the ./backend directory. ```bash npm run docker-db-only ``` -------------------------------- ### Install Firebase CLI Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Installs the Firebase Command Line Interface globally. ```bash pnpm add -g firebase-tools ``` -------------------------------- ### Initialize Challenge Setup Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Handles the overall process of setting up a challenge. This includes changing the UI page, fetching the challenge list, finding the specific challenge, and then calling the appropriate setup function based on the challenge type. It also includes error handling for challenges not found. ```javascript export async function setup(challengeName) { challengeLoading = true; if (UI.getActivePage() !== "pageTest") { UI.changePage("", true); } let list = await Misc.getChallengeList(); let challenge = list.filter((c) => c.name === challengeName)[0]; let notitext; try { if (challenge === undefined) { Notifications.add("Challenge not found", 0); ManualRestart.set(); TestLogic.restart(false, true); setTimeout(() => { $("#top .config").removeClass("hidden"); $(".page.pageTest").removeClass("hidden"); }, 250); return; } if (challenge.type === "customTime") { UpdateConfig.setTimeConfig(challenge.parameters[0], true); UpdateConfig.setMode("time", true); UpdateConfig.setDifficulty("normal", true); if (challenge.name === "englishMaster") { UpdateConfig.setLanguage("english_10k", true); UpdateConfig.setNumbers(true, true); UpdateConfig.setPunctuation(true, true); } } else if (challenge.type === "customWords") { UpdateConfig.setWordCount(challenge.parameters[0], true); UpdateConfig.setMode("words", true); UpdateConfig.setDifficulty("normal", true); } else if (challenge.type === "customText") { CustomText.setText(challenge.parameters[0].split(" ")); CustomText.setIsWordRandom(challenge.parameters[1]); CustomText.setWord(parseInt(challenge.parameters[2])); UpdateConfig.setMode("custom", true); UpdateConfig.setDifficulty("normal", true); } else if (challenge.type === "script") { let scriptdata = await fetch("/challenges/" + challenge.parameters[0]); scriptdata = await scriptdata.text(); let text = scriptdata.trim(); text = text.replace(/[ t ]/gm, " "); text = text.replace(/ +/gm, " "); CustomText.setText(text.split(" ")); CustomText.setIsWordRandom(false); UpdateConfig.setMode("custom", true); UpdateConfig.setDifficulty("normal", true); if (challenge.parameters[1] != null) { UpdateConfig.setTheme(challenge.parameters[1]); } if (challenge.parameters[2] != null) { Funbox.activate(challenge.parameters[2]); } } else if (challenge.type === "accuracy") { UpdateConfig.setTimeConfig(0, true); UpdateConfig.setMode("time", true); UpdateConfig.setDifficulty("master", true); } else if (challenge.type === "funbox") { UpdateConfig.setFunbox(challenge.parameters[0], true); UpdateConfig.setDifficulty("normal", true); if (challenge.parameters[1] === "words") { UpdateConfig.setWordCount(challenge.parameters[2], true); } else if (challenge.parameters[1] === "time") { UpdateConfig.setTimeConfig(challenge.parameters[2], true); } UpdateConfig.setMode(challenge.parameters[1], true); } } catch (e) { console.error(e); Notifications.add( `Something went wrong when setting up challenge: ${e.message}`, 0 ); } } ``` -------------------------------- ### Firebase Project Configuration Example Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Example content for the .firebaserc file, specifying the default Firebase project ID. ```json { "projects": { "default": "your-firebase-project-id" } } ``` -------------------------------- ### Install Specific Node.js Version with NVM Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Installs a specific version of Node.js using NVM. ```bash nvm install 24.11.0 nvm use 24.11.0 ``` -------------------------------- ### Preset API Route Setup Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Configures the API routes for managing user presets. It supports getting, adding, editing, and removing presets, each protected by specific rate limits and requiring authentication. ```javascript const { authenticateRequest } = require("../../middlewares/auth"); const PresetController = require("../controllers/preset"); const RateLimit = require("../../middlewares/rate-limit"); const { Router } = require("express"); const router = Router(); router.get( "/", RateLimit.presetsGet, authenticateRequest, PresetController.getPresets ); router.post( "/add", RateLimit.presetsAdd, authenticateRequest, PresetController.addPreset ); router.post( "/edit", RateLimit.presetsEdit, authenticateRequest, PresetController.editPreset ); router.post( "/remove", RateLimit.presetsRemove, authenticateRequest, PresetController.removePreset ); module.exports = router; ``` -------------------------------- ### Install PNPM Globally Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Installs the PNPM package manager globally with a specific version. ```bash npm i -g pnpm@10.28.1 ``` -------------------------------- ### Run Backend Only with Docker Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Starts the backend server using Docker. ```bash cd backend && npm run docker ``` -------------------------------- ### Configure Firebase Project Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Sets up the .firebaserc file by copying the example and updating the project ID. ```bash firebase projects:list ``` -------------------------------- ### Install and Use Specific Node.js Version with NVM Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Installs and switches to the Node.js version specified in the .nvmrc file using NVM. ```bash nvm install nvm use ``` -------------------------------- ### Run Frontend Only with Docker Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Starts the frontend development server using Docker. ```bash cd frontend && npm run docker ``` -------------------------------- ### Run Frontend Only Manually Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Starts only the frontend development server. ```bash npm run dev-fe ``` -------------------------------- ### Run Backend Only Manually Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Starts only the backend development server. ```bash npm run dev-be ``` -------------------------------- ### Show Configuration Panel with Animation Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Displays the configuration panel with a fade-in animation. Ensures any previous animations are stopped before starting a new one. ```javascript export function show() { $("#top .config") .stop(true, true) .removeClass("hidden") .css("opacity", 0) .animate( { opacity: 1, }, 125 ); } ``` -------------------------------- ### Get User Configuration Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Retrieves a user's configuration settings from the database. Returns null if no configuration is found. ```javascript static async getConfig(uid) { let config = await mongoDB().collection("configs").findOne({ uid }); // if (!config) throw new MonkeyError(404, "Config not found"); return config; } ``` -------------------------------- ### Get Paginated Results for a User Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Retrieves a paginated list of results for a given user from the 'results' collection. Results are sorted by timestamp in descending order. Supports 'start' and 'end' parameters for pagination, defaulting to 0 and 1000 respectively. Throws an error if no results are found. ```javascript static async getResults(uid, start, end) { start = start ?? 0; end = end ?? 1000; const result = await mongoDB() .collection("results") .find({ uid }) .sort({ timestamp: -1 }) .skip(start) .limit(end) .toArray(); // this needs to be changed to later take patreon into consideration if (!result) throw new MonkeyError(404, "Result not found"); return result; } ``` -------------------------------- ### Copy Backend Environment Example Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Copy the example.env file to .env in the backend directory to configure Docker database ports. Adjust DOCKER_DB_PORT, DOCKER_REDIS_PORT, and DOCKER_SERVER_PORT if these ports are already in use. ```bash cp example.env .env ``` -------------------------------- ### Set Test Start Time (JavaScript) Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Sets the start timestamp for the typing test. ```javascript export function setStart(s) { ``` -------------------------------- ### Configure Backend Environment Variables Source: https://context7.com/monkeytypegame/monkeytype/llms.txt Set up the `.env` file in the `backend/` directory with necessary credentials for MongoDB, Redis, and Firebase. An example file is provided for reference. ```bash cp backend/example.env backend/.env # Edit backend/.env with: # MONGO_URI=mongodb://localhost:27017/monkeytype # REDIS_URI=redis://localhost:6379 # FIREBASE_PROJECT_ID=... # FIREBASE_CLIENT_EMAIL=... # FIREBASE_PRIVATE_KEY=... ``` -------------------------------- ### Initialize Test Environment Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Sets up the testing environment by resetting state, updating configuration based on language support, and determining the number of words to display based on various modes and settings. ```javascript export async function init() { setActive(false); Replay.stopReplayRecording(); words.reset(); TestUI.setCurrentWordElementIndex(0); // accuracy = { // correct: 0, // incorrect: 0, // }; input.resetHistory(); input.resetCurrent(); let language = await Misc.getLanguage(Config.language); if (language && language.name !== Config.language) { UpdateConfig.setLanguage("english"); } if (!language) { UpdateConfig.setLanguage("english"); language = await Misc.getLanguage(Config.language); } if (Config.lazyMode === true && language.noLazyMode) { Notifications.add("This language does not support lazy mode.", 0); UpdateConfig.setLazyMode(false); } let wordsBound = 100; if (Config.showAllLines) { if (Config.mode === "quote") { wordsBound = 100; } else if (Config.mode === "custom") { if (CustomText.isWordRandom) { wordsBound = CustomText.word; } else if (CustomText.isTimeRandom) { wordsBound = 100; } else { wordsBound = CustomText.text.length; } } else if (Config.mode != "time") { wordsBound = Config.words; } } else { if (Config.mode === "words" && Config.words < wordsBound) { wordsBound = Config.words; } if ( Config.mode == "custom" && CustomText.isWordRandom && CustomText.word < wordsBound ) { wordsBound = CustomText.word; } if (Config.mode == "custom" && CustomText.isTimeRandom) { wordsBound = 100; } if ( Config.mode == "custom" && !CustomText.isWordRandom && !CustomText.isTimeRandom && CustomText.text.length < wordsBound ) { wordsBound = CustomText.text.length; } } if ( (Config.mode === "custom" && CustomText.isWordRandom && CustomText.word == 0) || (Config.mode === "custom" && CustomText.isTimeRandom && CustomText.time == 0) ) { wordsBound = 100; } if (Config.mode === "words" && Config.words === 0) { wordsBound = 100; } if (Config.funbox === "plus_one") { wordsBound = 2; if (Config.mode === "words" && Config.words < wordsBound) { wordsBound = Config.words; } } if (Config.funbox === "plus_two") { wordsBound = 3; if (Config.mode === "words" && Config.words < wordsBound) { wordsBound = Config.words; } } if ( (Config.mode == "time" || Config.mode == "words" || Config.mode == "custom") ) { let wordList = language.words; if (Config.mode == "custom") { ``` -------------------------------- ### GET /users/stats — Get typing statistics Source: https://context7.com/monkeytypegame/monkeytype/llms.txt Retrieves overall typing statistics for the user. ```APIDOC ## GET /users/stats — Get typing statistics ### Description Retrieves overall typing statistics for the user. ### Method GET ### Endpoint https://api.monkeytype.com/users/stats ### Response #### Success Response (200) - **message** (string) - Confirmation message. - **data** (object) - Typing statistics. - **completedTests** (integer) - Number of completed tests. - **startedTests** (integer) - Number of started tests. - **timeTyping** (integer) - Total time spent typing in seconds. ``` -------------------------------- ### Configure Frontend Environment Variables Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Create a .env file in the frontend directory to specify the backend URL for network access. Replace with your machine's IP address. ```bash BACKEND_URL="http://:5005" ``` -------------------------------- ### GET /users — Get current user data Source: https://context7.com/monkeytypegame/monkeytype/llms.txt Retrieves data for the currently authenticated user. ```APIDOC ## GET /users — Get current user data ### Description Retrieves data for the currently authenticated user. ### Method GET ### Endpoint https://api.monkeytype.com/users ### Response #### Success Response (200) - **message** (string) - Confirmation message. - **data** (object) - User data. - **name** (string) - Username. - **email** (string) - User's email. - **uid** (string) - User's unique ID. - **completedTests** (integer) - Number of completed tests. - **startedTests** (integer) - Number of started tests. - **timeTyping** (integer) - Total time spent typing in seconds. - **inboxUnreadSize** (integer) - Number of unread messages in the inbox. - **streak** (object) - User's current streak information. - **length** (integer) - Current streak length. - **lastResultTimestamp** (integer) - Timestamp of the last result. - **maxLength** (integer) - Maximum streak length achieved. ``` -------------------------------- ### Setup Script-Based Challenge Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Sets up a challenge using a script file. Use this when the challenge type is 'script'. It fetches the script content, processes it into words, and sets the mode and difficulty. Optionally, it can set a theme and activate a funbox. ```javascript else if (challenge.type === "script") { let scriptdata = await fetch("/challenges/" + challenge.parameters[0]); scriptdata = await scriptdata.text(); let text = scriptdata.trim(); text = text.replace(/[ t ]/gm, " "); text = text.replace(/ +/gm, " "); CustomText.setText(text.split(" ")); CustomText.setIsWordRandom(false); UpdateConfig.setMode("custom", true); UpdateConfig.setDifficulty("normal", true); if (challenge.parameters[1] != null) { UpdateConfig.setTheme(challenge.parameters[1]); } if (challenge.parameters[2] != null) { Funbox.activate(challenge.parameters[2]); } } ``` -------------------------------- ### GET /users/streak — Get user streak Source: https://context7.com/monkeytypegame/monkeytype/llms.txt Retrieves information about the user's current typing streak. ```APIDOC ## GET /users/streak — Get user streak ### Description Retrieves information about the user's current typing streak. ### Method GET ### Endpoint https://api.monkeytype.com/users/streak ### Response #### Success Response (200) - **message** (string) - Confirmation message. - **data** (object) - Streak information. - **length** (integer) - Current streak length. - **maxLength** (integer) - Maximum streak length achieved. - **lastResultTimestamp** (integer) - Timestamp of the last result contributing to the streak. ``` -------------------------------- ### Run Frontend and Backend Manually Source: https://github.com/monkeytypegame/monkeytype/blob/master/docs/CONTRIBUTING_ADVANCED.md Starts the local development website on port 3000 and the development server on port 5005. The website and server will automatically rebuild on changes in the src/ directory. ```bash npm run dev ``` -------------------------------- ### GET /users/testActivity — Get yearly test activity heatmap Source: https://context7.com/monkeytypegame/monkeytype/llms.txt Retrieves data for a yearly test activity heatmap. ```APIDOC ## GET /users/testActivity — Get yearly test activity heatmap ### Description Retrieves data for a yearly test activity heatmap. ### Method GET ### Endpoint https://api.monkeytype.com/users/testActivity ### Response #### Success Response (200) - **message** (string) - Confirmation message. - **data** (object) - Yearly test activity data, where keys are years and values are objects mapping days of the year to test counts. ``` -------------------------------- ### Core API Route Setup Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Sets up a basic API route for core functionalities, specifically a POST endpoint for '/test'. This route requires user authentication. ```javascript const { authenticateRequest } = require("../../middlewares/auth"); const { Router } = require("express"); const router = Router(); router.post("/test", authenticateRequest); module.exports = router; ``` -------------------------------- ### Initialize Test UI and Settings Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Resets UI elements, applies configuration settings based on 'funbox' mode, and initializes test components. Handles different game modes and special configurations. ```javascript TestUI.focusWords(); $("#monkey .fast").stop(true, true).css("opacity", 0); $("#monkey").stop(true, true).css({ animationDuration: "0s" }); $("#typingTest").css("opacity", 0).removeClass("hidden"); $("#wordsInput").val(" "); let shouldQuoteRepeat = false; if ( Config.mode === "quote" && Config.repeatQuotes === "typing" && failReason !== "" ) { shouldQuoteRepeat = true; } if (Config.funbox === "arrows") { UpdateConfig.setPunctuation(false, true); UpdateConfig.setNumbers(false, true); } else if (Config.funbox === "58008") { UpdateConfig.setNumbers(false, true); } else if (Config.funbox === "specials") { UpdateConfig.setPunctuation(false, true); UpdateConfig.setNumbers(false, true); } else if (Config.funbox === "ascii") { UpdateConfig.setPunctuation(false, true); UpdateConfig.setNumbers(false, true); } if (!withSameWordset && !shouldQuoteRepeat) { setRepeated(false); setPaceRepeat(repeatWithPace); setHasTab(false); await init(); PaceCaret.init(nosave); } else { setRepeated(true); setPaceRepeat(repeatWithPace); setActive(false); Replay.stopReplayRecording(); words.resetCurrentIndex(); input.reset(); if (Config.funbox === "plus_one" || Config.funbox === "plus_two") { Notifications.add( "Sorry, this funbox won't work with repeated tests.", 0 ); await Funbox.activate("none"); } else { await Funbox.activate(); } TestUI.showWords(); PaceCaret.init(); } failReason = ""; if (Config.mode === "quote") { setRepeated(false); } if (Config.keymapMode !== "off") { Keymap.show(); } else { Keymap.hide(); } document.querySelector("#miniTimerAndLiveWpm .wpm").innerHTML = "0"; document.querySelector("#miniTimerAndLiveWpm .acc").innerHTML = "100%"; document.querySelector("#miniTimerAndLiveWpm .burst").innerHTML = "0"; document.querySelector("#liveWpm").innerHTML = "0"; document.querySelector("#liveAcc").innerHTML = "100%"; document.querySelector("#liveBurst").innerHTML = "0"; if (Config.funbox === "memory") { Funbox.startMemoryTimer(); if (Config.keymapMode === "next") { UpdateConfig.setKeymapMode("react"); } } let mode2 = Misc.getMode2(); let fbtext = ""; if (Config.funbox !== "none") { fbtext = " " + Config.funbox; } $(".pageTest #premidTestMode").text( `${Config.mode} ${mode2} ${Config.language.replace(/_/g, " ")}${fbtext}` ); $(".pageTest #premidSecondsLeft").text(Config.time); if (Config.funbox === "layoutfluid") { UpdateConfig.setLayout( Config.customLayoutfluid ? Config.customLayoutfluid.split("#")[0] : "qwerty", true ); UpdateConfig.setKeymapLayout( Config.customLayoutfluid ? Config.customLayoutfluid.split("#")[0] : "qwerty", true ); Keymap.highlightKey( words .getCurrent() .substring(input.current.length, input.current.length + 1) .toString() .toUpperCase() ); } $("#result").addClass("hidden"); $("#testModesNotice").removeClass("hidden").css({ opacity: 1, }); // resetPaceCaret(); $("#typingTest") .css("opacity", 0) .removeClass("hidden") .stop(true, true) .animate( { opacity: 1, }, 125, () => { TestUI.setTestRestarting(false); // resetPaceCaret(); PbCrown.hide(); TestTimer.clear(); if ($("#commandLineWrapper").hasClass("hidden")) TestUI.focusWords(); // ChartController.result.update(); TestUI.updateModesNotice(); UI.setPageTransition(false); // console.log(TestStats.incompleteSeconds); // console.log(TestStats.restartCount); } ); } ); } ``` -------------------------------- ### Build Monkeytype Frontend Docker Image Source: https://github.com/monkeytypegame/monkeytype/blob/master/docker/BUILD.md Use this command from the project root to build the frontend Docker image. It specifies the build context, Dockerfile location, and tags the image. ```bash docker buildx build --progress=plain --no-cache -t monkeytype/monkeytype-frontend:latest . -f ./docker/frontend/Dockerfile ``` -------------------------------- ### GET /users/personalBests — Get personal bests Source: https://context7.com/monkeytypegame/monkeytype/llms.txt Retrieves the user's personal best records for different typing modes. ```APIDOC ## GET /users/personalBests — Get personal bests ### Description Retrieves the user's personal best records for different typing modes. ### Method GET ### Endpoint https://api.monkeytype.com/users/personalBests ### Query Parameters - **mode** (string) - Optional - The typing mode (e.g., "time", "words"). - **mode2** (string) - Optional - The specific mode setting (e.g., "60" for 60 seconds). ``` -------------------------------- ### Build Task Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Executes the 'clean' task followed by the 'compile' task to prepare the project for deployment. ```javascript task("build", series("clean", "compile")); ``` -------------------------------- ### Set Burst Start Time Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Records the start time of a typing burst. Used for calculating typing speed during rapid sequences of words. ```javascript export function setBurstStart(time) { currentBurstStart = time; } ``` -------------------------------- ### Get Public Service Announcement Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Fetches a public service announcement (PSA) from the database. This is a simple GET request to retrieve PSA data. ```javascript const PsaDAO = require("../../dao/psa"); class PsaController { static async get(req, res, next) { try { let data = await PsaDAO.get(); return res.status(200).json(data); } catch (e) { return next(e); } } } module.exports = PsaController; ``` -------------------------------- ### Initialize Firebase with Emulator Source: https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/challenges/sourcecode.txt Initializes Firebase with emulator support. Ensure Firebase SDKs are loaded before this script. ```javascript