This document provides practical examples of using captcha-canvas in various real-world scenarios.
const { createCaptcha } = require("captcha-canvas");
const fs = require("fs");
async function generateSimpleCaptcha() {
const { image, text } = createCaptcha(300, 100);
const buffer = await image;
fs.writeFileSync("captcha.png", buffer);
console.log("CAPTCHA text:", text);
}
generateSimpleCaptcha();
const { CaptchaGenerator } = require("captcha-canvas");
const fs = require("fs");
async function generateAdvancedCaptcha() {
const captcha = new CaptchaGenerator()
.setDimension(400, 150)
.setCaptcha({
text: "SECURE",
size: 60,
color: "#2c3e50"
})
.setTrace({
color: "#95a5a6",
size: 4
});
const buffer = await captcha.generate();
fs.writeFileSync("advanced-captcha.png", buffer);
console.log("CAPTCHA text:", captcha.text);
}
generateAdvancedCaptcha();
import { CaptchaGenerator, createCaptcha, SetCaptchaOption } from 'captcha-canvas';
import fs from 'fs';
async function typescriptExample(): Promise<void> {
// Using the simple function API
const { image, text } = createCaptcha(300, 100, {
captcha: { characters: 6, size: 40 },
trace: { color: '#95a5a6' }
});
const buffer = await image;
fs.writeFileSync('simple-captcha.png', buffer);
// Using the class API with type safety
const config: SetCaptchaOption = {
text: 'HELLO',
size: 50,
colors: ['#e74c3c', '#3498db']
};
const captcha = new CaptchaGenerator({ width: 350, height: 120 })
.setCaptcha(config);
const classBuffer = await captcha.generate();
fs.writeFileSync('class-captcha.png', classBuffer);
console.log('Generated text:', captcha.text);
}
const express = require("express");
const session = require("express-session");
const { createCaptcha } = require("captcha-canvas");
const app = express();
app.use(session({
secret: "your-secret-key",
resave: false,
saveUninitialized: true,
cookie: { maxAge: 300000 } // 5 minutes
}));
app.use(express.json());
app.use(express.static('public'));
// Generate CAPTCHA endpoint
app.get("/captcha", async (req, res) => {
try {
const { image, text } = createCaptcha(300, 100, {
captcha: {
characters: 6,
size: 40,
rotate: 15
},
trace: { opacity: 0.8, size: 3 },
decoy: { total: 25, opacity: 0.3 }
});
// Store solution in session with timestamp
req.session.captcha = {
solution: text,
timestamp: Date.now()
};
const buffer = await image;
res.type("png").send(buffer);
} catch (error) {
console.error('CAPTCHA generation error:', error);
res.status(500).json({ error: 'Failed to generate CAPTCHA' });
}
});
// Verify CAPTCHA endpoint
app.post("/verify", (req, res) => {
const { captcha } = req.body;
const sessionData = req.session.captcha;
// Check if CAPTCHA exists and hasn't expired (5 minutes)
if (!sessionData || Date.now() - sessionData.timestamp > 300000) {
return res.json({
success: false,
message: "CAPTCHA expired. Please refresh."
});
}
if (captcha && captcha.toUpperCase() === sessionData.solution) {
res.json({ success: true, message: "CAPTCHA verified!" });
} else {
res.json({ success: false, message: "Invalid CAPTCHA" });
}
// Clear session data
delete req.session.captcha;
});
// Refresh CAPTCHA endpoint
app.post("/captcha/refresh", async (req, res) => {
try {
const { image, text } = createCaptcha(300, 100);
req.session.captcha = {
solution: text,
timestamp: Date.now()
};
const buffer = await image;
res.type("png").send(buffer);
} catch (error) {
res.status(500).json({ error: 'Failed to refresh CAPTCHA' });
}
});
app.listen(3000, () => {
console.log("Server running on port 3000");
});
// pages/api/captcha.js
import { createCaptcha } from 'captcha-canvas';
import { withIronSession } from 'next-iron-session';
async function handler(req, res) {
if (req.method === 'GET') {
try {
const { image, text } = createCaptcha(300, 100, {
captcha: { characters: 6 },
decoy: { total: 30, opacity: 0.3 }
});
req.session.set('captcha', {
solution: text,
timestamp: Date.now()
});
await req.session.save();
const buffer = await image;
res.setHeader('Content-Type', 'image/png');
res.send(buffer);
} catch (error) {
res.status(500).json({ error: 'CAPTCHA generation failed' });
}
} else if (req.method === 'POST') {
const { captcha } = req.body;
const sessionData = req.session.get('captcha');
if (!sessionData || Date.now() - sessionData.timestamp > 300000) {
return res.json({ success: false, message: 'CAPTCHA expired' });
}
const isValid = captcha?.toUpperCase() === sessionData.solution;
if (isValid) {
req.session.destroy();
}
res.json({
success: isValid,
message: isValid ? 'Verified' : 'Invalid CAPTCHA'
});
}
}
export default withIronSession(handler, {
password: process.env.SECRET_COOKIE_PASSWORD,
cookieName: 'captcha-session'
});
const { CaptchaGenerator } = require("captcha-canvas");
async function rainbowCaptcha() {
const captcha = new CaptchaGenerator()
.setDimension(450, 150)
.setCaptcha({
text: "RAINBOW",
size: 55,
colors: [
"#e74c3c", // Red
"#f39c12", // Orange
"#f1c40f", // Yellow
"#27ae60", // Green
"#3498db", // Blue
"#9b59b6", // Purple
"#e91e63" // Pink
],
rotate: 10,
skew: true
})
.setTrace({
color: "#34495e",
size: 3,
opacity: 0.7
});
return {
buffer: await captcha.generate(),
text: captcha.text
};
}
const { CaptchaGenerator } = require("captcha-canvas");
async function multiStyledCaptcha() {
const captcha = new CaptchaGenerator({ width: 500, height: 150 })
.setCaptcha([
{
text: "SEC",
size: 60,
color: "#e74c3c",
font: "Arial",
rotate: 15
},
{
text: "URE",
size: 50,
color: "#27ae60",
font: "Times",
skew: true
},
{
text: "123",
size: 45,
color: "#3498db",
font: "Courier",
rotate: -10
}
])
.setTrace({ color: "#95a5a6", size: 4 });
return await captcha.generate();
}
const { CaptchaGenerator, resolveImage } = require("captcha-canvas");
async function noisyBackgroundCaptcha() {
// Create or load a noise pattern background
const background = await resolveImage("./assets/noise-pattern.jpg");
const captcha = new CaptchaGenerator()
.setDimension(400, 150)
.setBackground(background)
.setCaptcha({
text: "SECURE",
size: 60,
opacity: 0.95, // High opacity to stand out
colors: ["#ffffff", "#f8f9fa"], // Light colors for dark background
rotate: 12,
skew: true
})
.setTrace({
color: "#ecf0f1",
size: 3,
opacity: 0.8
})
.setDecoy({
total: 40,
opacity: 0.2,
color: "#bdc3c7"
});
return await captcha.generate();
}
const { CaptchaGenerator } = require("captcha-canvas");
async function fortKnoxCaptcha() {
const captcha = new CaptchaGenerator({ width: 500, height: 200 })
.setCaptcha({
characters: 8, // Longer text
size: 55,
rotate: 25, // More rotation
skew: true,
colors: [
"#2c3e50", "#e74c3c", "#27ae60",
"#f39c12", "#8e44ad", "#16a085"
],
opacity: 0.9
})
.setTrace({
size: 6, // Thicker trace lines
opacity: 0.9,
color: "#34495e"
})
.setDecoy({
total: 80, // Many decoys
opacity: 0.4,
size: 25,
color: "#7f8c8d"
});
return {
buffer: await captcha.generate(),
solution: captcha.text,
difficulty: "maximum"
};
}
const { createCaptchaSync } = require("captcha-canvas");
const fs = require("fs");
const path = require("path");
function generateCaptchaBatch(count, outputDir = "./captchas") {
// Ensure output directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
const captchas = [];
const startTime = Date.now();
for (let i = 0; i < count; i++) {
const { image, text } = createCaptchaSync(300, 100, {
captcha: {
characters: 6,
size: 40,
rotate: 15
},
decoy: { total: 25, opacity: 0.3 },
trace: { size: 3, opacity: 0.8 }
});
const filename = `captcha-${Date.now()}-${i}.png`;
const filepath = path.join(outputDir, filename);
fs.writeFileSync(filepath, image);
captchas.push({
id: i + 1,
filename,
filepath,
solution: text,
timestamp: Date.now()
});
// Progress logging
if ((i + 1) % 10 === 0) {
console.log(`Generated ${i + 1}/${count} CAPTCHAs`);
}
}
const duration = Date.now() - startTime;
console.log(`Batch complete: ${count} CAPTCHAs in ${duration}ms`);
// Save metadata
const metadata = {
count,
duration,
averageTime: duration / count,
captchas
};
fs.writeFileSync(
path.join(outputDir, 'metadata.json'),
JSON.stringify(metadata, null, 2)
);
return metadata;
}
// Generate 100 CAPTCHAs
const batch = generateCaptchaBatch(100);
console.log(`Average generation time: ${batch.averageTime.toFixed(2)}ms`);
const { CaptchaGenerator, createCaptchaSync } = require("captcha-canvas");
class CaptchaService {
constructor() {
this.fallbackEnabled = true;
this.retryAttempts = 3;
}
async generateRobustCaptcha(config = {}) {
const attempts = [];
// Primary generation attempt
for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
try {
const captcha = new CaptchaGenerator(config.dimensions)
.setCaptcha(config.captcha || { characters: 6 })
.setTrace(config.trace || {})
.setDecoy(config.decoy || {});
if (config.background) {
captcha.setBackground(config.background);
}
const buffer = await captcha.generate();
return {
success: true,
buffer,
text: captcha.text,
method: 'primary',
attempt
};
} catch (error) {
attempts.push({
attempt,
error: error.message,
timestamp: new Date().toISOString()
});
console.warn(`Attempt ${attempt} failed:`, error.message);
// Wait before retry (exponential backoff)
if (attempt < this.retryAttempts) {
await new Promise(resolve =>
setTimeout(resolve, Math.pow(2, attempt) * 100)
);
}
}
}
// Fallback to synchronous generation
if (this.fallbackEnabled) {
try {
console.log('Falling back to synchronous generation');
const { image, text } = createCaptchaSync(
config.dimensions?.width || 300,
config.dimensions?.height || 100,
{
captcha: config.captcha || { characters: 6 },
trace: config.trace || {},
decoy: config.decoy || {}
}
);
return {
success: true,
buffer: image,
text,
method: 'fallback',
attempts
};
} catch (fallbackError) {
attempts.push({
method: 'fallback',
error: fallbackError.message,
timestamp: new Date().toISOString()
});
}
}
// Complete failure
return {
success: false,
error: 'All generation attempts failed',
attempts
};
}
}
// Usage
const service = new CaptchaService();
async function handleCaptchaRequest(req, res) {
try {
const result = await service.generateRobustCaptcha({
dimensions: { width: 300, height: 100 },
captcha: { characters: 6, size: 40 },
trace: { size: 3 },
decoy: { total: 25, opacity: 0.3 }
});
if (result.success) {
req.session.captcha = result.text;
res.type('png').send(result.buffer);
} else {
console.error('CAPTCHA generation failed:', result);
res.status(500).json({
error: 'CAPTCHA generation failed',
details: result.attempts
});
}
} catch (error) {
console.error('CAPTCHA request error:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
const { createCaptcha, createCaptchaSync, CaptchaGenerator } = require("captcha-canvas");
describe("CAPTCHA Generation Tests", () => {
describe("Basic Functionality", () => {
test("should generate CAPTCHA with correct format", async () => {
const { image, text } = createCaptcha(300, 100);
const buffer = await image;
expect(buffer).toBeInstanceOf(Buffer);
expect(buffer.length).toBeGreaterThan(0);
expect(text).toMatch(/^[A-F0-9]+$/);
expect(text.length).toBeGreaterThan(0);
});
test("should generate synchronous CAPTCHA", () => {
const { image, text } = createCaptchaSync(300, 100);
expect(image).toBeInstanceOf(Buffer);
expect(text).toMatch(/^[A-F0-9]+$/);
});
test("should respect custom dimensions", async () => {
const { image } = createCaptcha(400, 200);
const buffer = await image;
// PNG signature check
expect(buffer.slice(0, 8)).toEqual(
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
);
});
});
describe("CaptchaGenerator Class", () => {
test("should create instance with default dimensions", () => {
const captcha = new CaptchaGenerator();
expect(captcha).toBeInstanceOf(CaptchaGenerator);
});
test("should handle method chaining", async () => {
const captcha = new CaptchaGenerator()
.setDimension(350, 120)
.setCaptcha({ text: "TEST123" })
.setTrace({ color: "#ff0000" });
const buffer = await captcha.generate();
expect(buffer).toBeInstanceOf(Buffer);
expect(captcha.text).toBe("TEST123");
});
test("should generate random text when characters specified", async () => {
const captcha = new CaptchaGenerator()
.setCaptcha({ characters: 8 });
await captcha.generate();
expect(captcha.text).toHaveLength(8);
expect(captcha.text).toMatch(/^[A-F0-9]+$/);
});
test("should handle multi-styled text arrays", async () => {
const captcha = new CaptchaGenerator()
.setCaptcha([
{ text: "HE", color: "#ff0000" },
{ text: "LLO", color: "#00ff00" }
]);
await captcha.generate();
expect(captcha.text).toBe("HELLO");
});
});
describe("Configuration Validation", () => {
test("should handle invalid dimensions gracefully", () => {
expect(() => {
new CaptchaGenerator({ width: -100, height: -50 });
}).not.toThrow();
});
test("should throw error for array captcha without text", () => {
expect(() => {
new CaptchaGenerator().setCaptcha([
{ size: 40 }, // Missing text property
{ text: "WORLD" }
]);
}).toThrow("Each captcha option in array must have a text property.");
});
});
describe("Performance Tests", () => {
test("should generate CAPTCHA within reasonable time", async () => {
const startTime = Date.now();
const { image } = createCaptcha(300, 100);
await image;
const duration = Date.now() - startTime;
expect(duration).toBeLessThan(1000); // Should complete within 1 second
});
test("should handle batch generation efficiently", () => {
const startTime = Date.now();
const results = [];
for (let i = 0; i < 10; i++) {
const { image, text } = createCaptchaSync(300, 100);
results.push({ image, text });
}
const duration = Date.now() - startTime;
expect(results).toHaveLength(10);
expect(duration).toBeLessThan(2000); // 10 CAPTCHAs in under 2 seconds
});
});
});
const { CaptchaGenerator, resolveImage } = require("captcha-canvas");
const LRU = require("lru-cache");
class OptimizedCaptchaService {
constructor(options = {}) {
this.backgroundCache = new LRU({
max: options.maxBackgrounds || 10,
ttl: options.backgroundTTL || 1000 * 60 * 60 // 1 hour
});
this.generatorPool = [];
this.poolSize = options.poolSize || 5;
// Pre-create generator instances
for (let i = 0; i < this.poolSize; i++) {
this.generatorPool.push(new CaptchaGenerator());
}
this.currentGenerator = 0;
}
async loadBackground(path) {
if (!this.backgroundCache.has(path)) {
try {
const image = await resolveImage(path);
this.backgroundCache.set(path, image);
} catch (error) {
console.error(`Failed to load background: ${path}`, error);
return null;
}
}
return this.backgroundCache.get(path);
}
getGenerator() {
const generator = this.generatorPool[this.currentGenerator];
this.currentGenerator = (this.currentGenerator + 1) % this.poolSize;
return generator;
}
async generateOptimized(config = {}) {
const generator = this.getGenerator();
// Reset generator state
generator
.setDimension(config.width || 300, config.height || 100)
.setCaptcha(config.captcha || { characters: 6 })
.setTrace(config.trace || { size: 3, opacity: 0.8 })
.setDecoy(config.decoy || { total: 25, opacity: 0.3 });
// Handle background if specified
if (config.backgroundPath) {
const background = await this.loadBackground(config.backgroundPath);
if (background) {
generator.setBackground(background);
}
}
return {
buffer: await generator.generate(),
text: generator.text
};
}
}
// Usage
const service = new OptimizedCaptchaService({
poolSize: 3,
maxBackgrounds: 5
});
const { CaptchaGenerator } = require("captcha-canvas");
class ResponsiveCaptchaService {
constructor() {
this.presets = {
mobile: {
dimensions: { width: 250, height: 80 },
captcha: {
characters: 5,
size: 28,
rotate: 8,
skew: false // Less distortion for mobile
},
trace: { size: 2, opacity: 0.6 },
decoy: { total: 15, opacity: 0.2 }
},
tablet: {
dimensions: { width: 320, height: 100 },
captcha: {
characters: 6,
size: 35,
rotate: 12
},
trace: { size: 2.5, opacity: 0.7 },
decoy: { total: 20, opacity: 0.25 }
},
desktop: {
dimensions: { width: 400, height: 120 },
captcha: {
characters: 6,
size: 45,
rotate: 15
},
trace: { size: 3, opacity: 0.8 },
decoy: { total: 30, opacity: 0.3 }
},
accessibility: {
dimensions: { width: 450, height: 150 },
captcha: {
characters: 5,
size: 60,
rotate: 5, // Minimal rotation
skew: false, // No skewing
color: "#000000" // High contrast
},
trace: { size: 2, opacity: 0.3 },
decoy: { total: 10, opacity: 0.1 }
}
};
}
async generate(deviceType = 'desktop', customConfig = {}) {
const preset = this.presets[deviceType] || this.presets.desktop;
// Merge preset with custom configuration
const config = {
dimensions: { ...preset.dimensions, ...customConfig.dimensions },
captcha: { ...preset.captcha, ...customConfig.captcha },
trace: { ...preset.trace, ...customConfig.trace },
decoy: { ...preset.decoy, ...customConfig.decoy }
};
const captcha = new CaptchaGenerator(config.dimensions)
.setCaptcha(config.captcha)
.setTrace(config.trace)
.setDecoy(config.decoy);
return {
buffer: await captcha.generate(),
text: captcha.text,
deviceType,
config
};
}
// Auto-detect device type from user agent
detectDeviceType(userAgent) {
const ua = userAgent.toLowerCase();
if (/mobile|android|iphone|ipod|blackberry|windows phone/.test(ua)) {
return 'mobile';
} else if (/tablet|ipad/.test(ua)) {
return 'tablet';
} else {
return 'desktop';
}
}
// Generate accessibility-friendly CAPTCHA
async generateAccessible(options = {}) {
return this.generate('accessibility', {
captcha: {
...options,
// Force accessibility settings
rotate: Math.min(options.rotate || 5, 5),
skew: false,
color: options.color || "#000000"
}
});
}
}
// Express.js integration
const responsiveService = new ResponsiveCaptchaService();
app.get('/captcha', async (req, res) => {
try {
const deviceType = responsiveService.detectDeviceType(
req.headers['user-agent'] || ''
);
const accessibility = req.query.accessibility === 'true';
const result = accessibility
? await responsiveService.generateAccessible()
: await responsiveService.generate(deviceType);
req.session.captcha = {
solution: result.text,
deviceType: result.deviceType,
timestamp: Date.now()
};
res.type('png').send(result.buffer);
} catch (error) {
console.error('Responsive CAPTCHA error:', error);
res.status(500).json({ error: 'CAPTCHA generation failed' });
}
});
This comprehensive usage guide covers all the major use cases and patterns for the captcha-canvas library, from basic generation to advanced enterprise-level implementations with error handling, performance optimization, and accessibility considerations.