Skip to content

@cap.js/server

@cap.js/server is Cap's server-side library. It helps you create and validate challenges for your users. Start by installing it using bun (recommended), npm, or pnpm:

bash
bun add @cap.js/server
bash
npm i @cap.js/server
bash
pnpm i @cap.js/server

Example code

js
import { Elysia } from "elysia";
import Cap from "@cap.js/server";

const cap = new Cap({
  tokens_store_path: ".data/tokensList.json",
});

new Elysia()
  .post("/api/challenge", () => {
    return cap.createChallenge();
  })

  .post("/api/redeem", async ({ body, set }) => {
    const { token, solutions } = body;

    if (!token || !solutions) {
      set.status = 400;
      return { success: false };
    }

    return await cap.redeemChallenge({ token, solutions });
  })
  .listen(3000);

console.log(`🦊 Elysia is running at http://localhost:3000`);
js
import Fastify from "fastify";
import Cap from "@cap.js/server";

const fastify = Fastify();
const cap = new Cap({
  tokens_store_path: ".data/tokensList.json",
});

fastify.post("/api/challenge", (req, res) => {
  res.send(cap.createChallenge());
});

fastify.post("/api/redeem", async (req, res) => {
  const { token, solutions } = req.body;
  if (!token || !solutions) {
    return res.code(400).send({ success: false });
  }

  res.send(await cap.redeemChallenge({ token, solutions }));
});

fastify.listen({ port: 3000, host: "0.0.0.0" }).then(() => {
  console.log("Server is running on http://localhost:3000");
});
js
import Cap from "@cap.js/server";

const cap = new Cap({
  tokens_store_path: ".data/tokensList.json",
});

Bun.serve({
  port: 3000,
  routes: {
    "/api/challenge": {
      POST: () => {
        return Response.json(cap.createChallenge());
      },
    },
    "/api/redeem": {
      POST: async (req) => {
        const body = await req.json();
        const { token, solutions } = body;

        if (!token || !solutions) {
          return Response.json({ success: false }, { status: 400 });
        }

        return Response.json(await cap.redeemChallenge({ token, solutions }));
      },
    },
  },
});

console.log(`Server running at http://localhost:3000`);
js
import { Hono } from "hono";
import Cap from "@cap.js/server";

const app = new Hono();
const cap = new Cap({
  tokens_store_path: ".data/tokensList.json",
});

app.post("/api/challenge", (c) => {
  return c.json(cap.createChallenge());
});

app.post("/api/redeem", async (c) => {
  const { token, solutions } = await c.req.json();
  if (!token || !solutions) {
    return c.json({ success: false }, 400);
  }

  return c.json(await cap.redeemChallenge({ token, solutions }));
});

export default {
  port: 3000,
  fetch: app.fetch,
};
js
import express from "express";
import Cap from "@cap.js/server";

const app = express();
app.use(express.json());

const cap = new Cap({
  tokens_store_path: ".data/tokensList.json",
});

app.post("/api/challenge", (req, res) => {
  res.json(cap.createChallenge());
});

app.post("/api/redeem", async (req, res) => {
  const { token, solutions } = req.body;
  if (!token || !solutions) {
    return res.status(400).json({ success: false });
  }
  res.json(await cap.redeemChallenge({ token, solutions }));
});

app.listen(3000, () => {
  console.log("Listening on port 3000");
});

WARNING

These example codes don't have ratelimiting for simplicity. Make sure to add proper ratelimiting to your endpoints to prevent abuse.

Then, you can verify the CAPTCHA tokens on your server by calling the await cap.validateToken("<token>") method. Example:

js
const { success } = await cap.validateToken("9363220f..."); 

if (success) {
  console.log("Valid token");
} else {
  console.log("Invalid token");
}

Supported methods and arguments

The following methods are supported:

new Cap({ ... })

Creates a new Cap instance.

Arguments

json
{
  "tokens_store_path": ".data/tokensList.json",
  "noFSState": false,
  "state": {
    "challengesList": {},
    "tokensList": {}
  }
}

You can always access or set the options of the Cap class by accessing or modifying the cap.config object.

cap.createChallenge({ ... })

Arguments

json
{
  "challengeCount": 50,
  "challengeSize": 32,
  "challengeDifficulty": 4,
  "expiresMs": 600000
}

Response: { challenge, expires }

if noFSState is set to true, the state will be stored in memory only. You can use this together with setting config.state to use a custom db such as redis for storing tokens. Added by #16

cap.redeemChallenge({ ... })

json
{
  token,
  solutions
}

Response: { success, token }

await cap.validateToken("...", { ... })

Arguments:

json
{
  "keepToken": false
}

Response: { success }

Built in Europe 🇪🇺
Released under the Apache 2.0 License.