@codebundlesbyvik/simple-maths-captcha v1.1.0
Simple Maths CAPTCHA
Easy to use, easy to solve CAPTCHA.
Table of Contents
Usage
# Install package from npm
npm install @codebundlesbyvik/simple-maths-captchaIf you're not using a module bundler then either:
- Download the latest release from the GitHub releases page, or
 - Load the JavaScript via the jsdelivr CDN.
 
For the example below I assume the main JavaScript file is processed by a module bundler.
import SimpleMathsCaptcha from "@codebundlesbyvik/simple-maths-captcha";
import { convertUnixTimeFormatToMs } from "@codebundlesbyvik/ntp-sync";
new SimpleMathsCaptcha({
    activatorButtonEl: document.querySelector("#simple-maths-captcha-activator-button"),
    generatorEndpointUrl: "./api/simple-maths-captcha/generate-problem.php",
    ntpOptions: {
        t1EndpointUrl: "./api/ntp/get-server-time.php",
        t1CalcFn: async function t1CalcFn(response: Response) {
            const data = (await response.json()) as { req_received_time: number };
            return convertUnixTimeFormatToMs(data.req_received_time);
        },
        // Providing a t2CalcFn for greater accuracy is recommended but not required.
        t2CalcFn: function t2CalcFn(resHeaders: Headers) {
            // Header value example: t=1747777363406069 D=110
            const header = resHeaders.get("Response-Timing");
            if (!header) {
                return null;
            }
            const reqReceivedTime = /\bt=([0-9]+)\b/.exec(header);
            const reqProcessingTime = /\bD=([0-9]+)\b/.exec(header);
            if (!reqReceivedTime || !reqProcessingTime) {
                return null;
            }
            const resTransmitTime =
                Number.parseInt(reqReceivedTime[1]) + Number.parseInt(reqProcessingTime[1]);
            return convertUnixTimeFormatToMs(resTransmitTime);
        },
    }
});The CAPTCHA initializes on instance creation. On press of the activator button a NTP sync is performed after which a maths problem is requested. The problem is inserted in the DOM, alongside 3 <input>s: the main one in which the user has to provide the answer and 2 hidden ones used to store the problem's individual digits. After the invalidation time provided by the back end has passed the CAPTCHA is automatically deactivated.
The exact implementation of the back end components is up to you. If you need some inspiration you can check out how I did it in PHP for my own website.
Browser support
Requires an ECMAScript 2022 (ES13) compatible browser. Practically speaking, all browsers released in 2021 and onwards are fully supported.
Instance options
| Property | Type | Default | Description | 
|---|---|---|---|
activatorButtonEl Required | HTMLButtonElement | HTMLInputElement | - | Button which the user presses to activate the CAPTCHA. Must be a child of the <form>. | 
generatorEndpointUrl Required if generatorEndpoint not provided. | string | - | URL of the endpoint of the problem generator. Should return an array with a length of 3, the first 2 items being the digits that compose the maths problem and the final item being a Unix timestamp in milliseconds after which the problem is invalidated. | 
generatorEndpoint Required if generatorEndpointUrl not provided. | @codebundlesbyvik/js-helpers fetchWithTimeout parameters. | - | Parameters for the problem generator fetcher. | 
ntpOptions Required | @codebundlesbyvik/ntp-sync options. | - | Options used by the NTP sync library. | 
baseId | string | - | Automatically suffixed with simple-maths-captcha. Set as HTML id & name on the <input>s generated by the instance, after automatic addition of the <input>'s role. E.g. baseId: "contact-form" results in contact-form-simple-maths-captcha-answer added as HTML id & name to the main <input> element. | 
answerInputElEventHandlers | addEventListener() parameters. | undefined | Event handlers with these options will be added to the main <input> element. | 
Methods
.isCaptchaInputEl(id: string)
Check if the provided id matches the id of a CAPTCHA <input> element.
The following methods are automatically called when needed.
.activate()
Performs NTP sync, requests a new maths problem, inserts it and 3 <input>s (one of which used by the user for providing the answer) in the DOM and schedules .deactivate() call.
.deactivate()
Removes the <input> for providing the answer from the DOM and inserts the activator button.
License
Mozilla Public License 2.0 © 2025 Viktor Chin-Kon-Sung