npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scr-slider-captcha

v1.1.2

Published

A Vue3 slider image captcha component with Node.js verification backend

Readme

scr-slider-captcha

一个 Vue3 图片滑块验证码组件 + Node.js 后端验证服务。前端采集用户拖动轨迹,通过 Web Worker 使用 RSA/AES-GCM 混合加密后发送至后端验证。

安装

npm install scr-slider-captcha

前端使用

注意:导入组件时会自动引入样式文件,无需手动 import CSS。

完整流程

<script setup>
import { ref, onMounted } from 'vue';
import { SliderCaptcha } from 'scr-slider-captcha/frontend';

const bgBase64 = ref('');
const puzzleBase64 = ref('');
const targetY = ref(0);
const puzzleSize = ref(50);
const publicKey = ref('');
const captchaId = ref('');

const fetchCaptcha = async () => {
  const res = await fetch('/api/captcha/create', { method: 'POST' });
  const data = await res.json();
  bgBase64.value = data.bgBase64;
  puzzleBase64.value = data.puzzleBase64;
  targetY.value = data.targetY;
  puzzleSize.value = data.puzzleSize;
  publicKey.value = data.publicKey;
  captchaId.value = data.captchaId;
};

const handleVerify = async (encryptedTrajectory) => {
  const res = await fetch('/api/captcha/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ captchaId: captchaId.value, encryptedTrajectory })
  });
  const result = await res.json();
  if (result.success) {
    // 验证通过
  } else {
    fetchCaptcha(); // 失败后刷新验证码
  }
};

onMounted(fetchCaptcha);
</script>

<template>
  <SliderCaptcha
    :bg-base64="bgBase64"
    :puzzle-base64="puzzleBase64"
    :target-y="targetY"
    :puzzle-size="puzzleSize"
    :width="300"
    :height="150"
    :public-key="publicKey"
    @verify="handleVerify"
  />
</template>

SliderCaptcha Props

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | bgBase64 | String | '' | 带镂空的背景图片 base64(不含 data:image 头) | | puzzleBase64 | String | '' | 拼图块图片 base64 | | targetY | Number | 50 | 拼图块在背景中的垂直位置(像素) | | puzzleSize | Number | 50 | 拼图块尺寸(像素) | | width | Number | 300 | 验证码宽度(像素) | | height | Number | 150 | 验证码高度(像素) | | publicKey | String | '' | RSA 公钥 PEM 字符串,传入后启用加密 |

SliderCaptcha Events

| 事件 | 参数 | 说明 | |------|------|------| | verify | encryptedData: String | 用户拖动完成后触发,参数为加密后的轨迹数据,需发送至后端验证 |

加密说明

  • 轨迹数据在 Web Worker 中加密,不阻塞主线程
  • 数据量 < 400 字符:RSA-OAEP 直接加密(前缀 rsa:
  • 数据量 >= 400 字符:AES-GCM 加密轨迹 + RSA-OAEP 加密 AES 密钥(前缀 hybrid:
  • 未传入公钥:base64 编码明文(前缀 raw:

后端使用

const { createCaptcha, verifyCaptcha } = require('scr-slider-captcha/backend');

// 创建验证码
const captcha = await createCaptcha({
  width: 300,
  height: 150,
  puzzleSize: 50,
  publicKey: '-----BEGIN PUBLIC KEY-----\n...'  // 传入公钥返回给前端
});

// 验证轨迹
const result = verifyCaptcha(
  captchaId,
  encryptedTrajectory,
  '-----BEGIN PRIVATE KEY-----\n...'  // 传入私钥解密
);

createCaptcha

异步函数,生成滑块验证码。

const captcha = await createCaptcha(options);

options

| 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | width | Number | 300 | 图片宽度 | | height | Number | 150 | 图片高度 | | puzzleSize | Number | 50 | 拼图块尺寸 | | publicKey | String | '' | RSA 公钥,传入后将返回给前端用于 Web Worker 加密 |

返回值

{
  "captchaId": "uuid-string",
  "bgBase64": "base64-string",
  "puzzleBase64": "base64-string",
  "targetY": 45,
  "puzzleSize": 50,
  "publicKey": "-----BEGIN PUBLIC KEY-----\n..."
}

verifyCaptcha

同步函数,验证滑块轨迹。

const result = verifyCaptcha(captchaId, encryptedTrajectory, privateKey, scoreThreshold);

参数

| 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | captchaId | String | - | createCaptcha 返回的验证码 ID | | encryptedTrajectory | String | - | 前端 SliderCaptcha 组件 @verify 事件返回的加密数据 | | privateKey | String | null | RSA 私钥 PEM 字符串,用于解密轨迹数据 | | scoreThreshold | Number | 50 | 分数阈值,轨迹分数高于此值则验证通过(0-100) |

返回值

{ "success": true }

成功返回 { success: true },失败返回 { success: false }

验证规则

  1. 位置偏差:滑块最终位置与目标位置偏差 > 30px 则失败
  2. 无拖动:最终位置与目标位置完全一致(偏差为 0)判定为机器操作
  3. 轨迹分析
    • 总耗时 < 200ms 判定为过快
    • 速度标准差 < 0.3 判定为速度过于均匀(类机器)
    • 速度范围 < 0.5 判定为缺乏变化
    • 方向变化超过轨迹点 30% 判定为抖动异常
    • Y 轴方差 < 1 判定为完全水平(类机器)
    • 缺少加减速曲线判定为非人操作
    • 综合评分 < 50 分验证失败

密钥生成

使用 OpenSSL 生成 4096-bit RSA 密钥对:

# 生成私钥
openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:4096

# 提取公钥
openssl pkey -in private.pem -pubout -out public.pem

密钥由调用方管理,前端组件和后端包不包含密钥文件。通过 publicKeyprivateKey 参数动态传入。

快速启动

后端服务器

const express = require('express');
const cors = require('cors');
const fs = require('fs');
const { createCaptcha, verifyCaptcha } = require('scr-slider-captcha/backend');

const app = express();
app.use(cors());
app.use(express.json({ limit: '10mb' }));

const publicKey = fs.readFileSync('./keys/public.pem', 'utf8');
const privateKey = fs.readFileSync('./keys/private.pem', 'utf8');

app.post('/api/captcha/create', async (req, res) => {
  const result = await createCaptcha({ ...req.body, publicKey });
  res.json(result);
});

app.post('/api/captcha/verify', (req, res) => {
  const { captchaId, encryptedTrajectory } = req.body;
  const result = verifyCaptcha(captchaId, encryptedTrajectory, privateKey);
  res.json(result);
});

app.listen(3000);

前端集成

<script setup>
import { ref } from 'vue';
import { SliderCaptcha } from 'scr-slider-captcha/frontend';

const bgBase64 = ref('');
const puzzleBase64 = ref('');
const targetY = ref(0);
const puzzleSize = ref(50);
const publicKey = ref('');
const captchaId = ref('');

const fetchCaptcha = async () => {
  const res = await fetch('http://localhost:3000/api/captcha/create', { method: 'POST' });
  const data = await res.json();
  bgBase64.value = data.bgBase64;
  puzzleBase64.value = data.puzzleBase64;
  targetY.value = data.targetY;
  puzzleSize.value = data.puzzleSize;
  publicKey.value = data.publicKey;
  captchaId.value = data.captchaId;
};

const handleVerify = async (encryptedTrajectory) => {
  const res = await fetch('http://localhost:3000/api/captcha/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ captchaId: captchaId.value, encryptedTrajectory })
  });
  const result = await res.json();
  if (result.success) {
    // 验证通过
  } else {
    fetchCaptcha(); // 刷新验证码
  }
};

onMounted(fetchCaptcha);
</script>

<template>
  <SliderCaptcha
    :bg-base64="bgBase64"
    :puzzle-base64="puzzleBase64"
    :target-y="targetY"
    :puzzle-size="puzzleSize"
    :public-key="publicKey"
    @verify="handleVerify"
  />
</template>

包结构

scr-slider-captcha/
├── frontend/
│   ├── scr-slider-captcha.es.js    ESM 模块(已混淆压缩)
│   ├── scr-slider-captcha.umd.js   UMD 模块
│   └── style.css                    组件样式
├── backend/
│   ├── index.js                     主入口(createCaptcha, verifyCaptcha)
│   ├── verifier.js                  轨迹验证逻辑
│   ├── crypto.js                    解密模块
│   └── imageProcessor.js            图片获取
└── package.json

依赖

| 依赖 | 版本 | 用途 | |------|------|------| | canvas | ^3.2.3 | 后端图片裁切与拼图生成(Node.js Canvas) | | uuid | ^9.0.0 | 后端生成验证码 ID |

前端零依赖,后端仅依赖 Canvas 图片处理和 UUID 生成器。

许可证

MIT