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

scanwith-web-sdk

v3.0.0

Published

Web SDK for ScanWithWeb - Connect web applications to local TWAIN scanners via WebSocket

Readme

ScanWith Web SDK

npm version TypeScript License: MIT

一个用于通过 WebSocket 连接 Web 应用与本地 TWAIN 扫描仪的 TypeScript SDK。支持 React、Vue 和原生 JavaScript/TypeScript。

English Documentation

特性

  • TypeScript 优先: 包含完整的类型定义
  • 多框架支持: 核心客户端、React Hooks 和 Vue 3 Composables
  • 双协议支持: 根据页面协议自动选择 WS (HTTP) 或 WSS (HTTPS)
  • 事件驱动: 订阅连接、扫描进度和图像事件
  • 自动重连: 可配置的断线自动重连
  • 完整扫描控制: DPI、色彩模式、纸张大小、双面、ADF 支持
  • 令牌认证: 安全的会话认证机制

环境要求

  • ScanWithWeb 服务: 必须在本地机器上运行 (Windows)
  • Node.js: >= 16.0.0
  • 浏览器: 支持 WebSocket 的现代浏览器

安装

npm install scanwith-web-sdk
# 或
yarn add scanwith-web-sdk
# 或
pnpm add scanwith-web-sdk

快速开始

原生 TypeScript/JavaScript

import { ScanClient } from 'scanwith-web-sdk';

const client = new ScanClient({
  host: 'localhost',
  secure: window.location.protocol === 'https:',
  autoReconnect: true,
});

// 监听扫描图像
client.on('image', (image) => {
  const url = URL.createObjectURL(image.blob);
  console.log('扫描页面:', image.pageNumber, url);
});

client.on('scanComplete', (totalPages) => {
  console.log(`扫描完成: ${totalPages} 页`);
});

// 连接并扫描
async function startScan() {
  await client.connect();
  await client.authenticate();

  const scanners = await client.getScanners();
  console.log('可用扫描仪:', scanners);

  if (scanners.length > 0) {
    await client.selectScanner(scanners[0].name);
    await client.scan({
      dpi: 300,
      pixelType: 'RGB',
      paperSize: 'A4',
    });
  }
}

startScan();

React

import { useScanClient, useScanners, useScan } from 'scanwith-web-sdk/react';

function ScannerApp() {
  const { client, isConnected, isAuthenticated, error } = useScanClient({
    secure: true,
    autoConnect: true,
  });

  const { scanners, selectedScanner, selectScanner, loading } = useScanners(client);
  const { scan, stop, images, scanning, currentPage } = useScan(client);

  if (error) return <div>错误: {error.message}</div>;
  if (!isConnected) return <div>正在连接...</div>;
  if (!isAuthenticated) return <div>正在认证...</div>;

  return (
    <div>
      <select
        onChange={(e) => selectScanner(e.target.value)}
        disabled={loading}
      >
        <option value="">选择扫描仪</option>
        {scanners.map((s) => (
          <option key={s.id} value={s.name}>{s.name}</option>
        ))}
      </select>

      <button onClick={() => scan({ dpi: 300 })} disabled={scanning || !selectedScanner}>
        {scanning ? `正在扫描第 ${currentPage} 页...` : '扫描'}
      </button>

      <button onClick={stop} disabled={!scanning}>停止</button>

      <div className="gallery">
        {images.map((img, i) => (
          <img key={i} src={URL.createObjectURL(img.blob)} alt={`第 ${i + 1} 页`} />
        ))}
      </div>
    </div>
  );
}

Vue 3

<script setup lang="ts">
import { useFullScanner } from 'scanwith-web-sdk/vue';

const {
  isConnected,
  isAuthenticated,
  error,
  scanners,
  scanning,
} = useFullScanner({ secure: true });

const handleScan = async () => {
  await scanning.scan({ dpi: 300, pixelType: 'RGB' });
};
</script>

<template>
  <div v-if="error">错误: {{ error.message }}</div>
  <div v-else-if="!isConnected">正在连接...</div>
  <div v-else-if="!isAuthenticated">正在认证...</div>
  <div v-else>
    <select @change="scanners.selectScanner(($event.target as HTMLSelectElement).value)">
      <option value="">选择扫描仪</option>
      <option
        v-for="s in scanners.scanners"
        :key="s.id"
        :value="s.name"
      >
        {{ s.name }}
      </option>
    </select>

    <button @click="handleScan" :disabled="scanning.scanning">
      {{ scanning.scanning ? `正在扫描第 ${scanning.currentPage} 页...` : '扫描' }}
    </button>

    <button @click="scanning.stop" :disabled="!scanning.scanning">
      停止
    </button>

    <div class="gallery">
      <img
        v-for="(url, i) in scanning.imageUrls"
        :key="i"
        :src="url"
        :alt="`第 ${i + 1} 页`"
      />
    </div>
  </div>
</template>

API 参考

ScanClient

WebSocket 通信的核心客户端类。

构造函数选项

interface ScanClientOptions {
  host?: string;              // 默认: 'localhost'
  port?: number;              // 默认: 8180 (WS) 或 8181 (WSS)
  secure?: boolean;           // 默认: 根据页面协议自动检测
  autoReconnect?: boolean;    // 默认: true
  reconnectInterval?: number; // 默认: 3000 (毫秒)
  maxReconnectAttempts?: number; // 默认: 5
  requestTimeout?: number;    // 默认: 30000 (毫秒)
}

方法

| 方法 | 描述 | 返回值 | |------|------|--------| | connect() | 连接到 WebSocket 服务器 | Promise<void> | | disconnect() | 断开连接 | void | | authenticate(clientId?) | 进行身份认证 | Promise<AuthResponse> | | getScanners() | 获取可用扫描仪列表 | Promise<ScannerInfo[]> | | selectScanner(name) | 按名称选择扫描仪 | Promise<ScanResponse> | | getCapabilities() | 获取扫描仪能力 | Promise<ScannerCapabilities> | | scan(settings?) | 开始扫描 | Promise<ScanResult> | | stopScan() | 停止当前扫描 | Promise<ScanResponse> | | ping() | 检查连接健康状态 | Promise<ScanResponse> | | on(event, callback) | 订阅事件 | () => void (取消订阅函数) | | off(event, callback) | 取消订阅事件 | void | | once(event, callback) | 订阅一次性事件 | void |

属性

| 属性 | 类型 | 描述 | |------|------|------| | connectionState | ConnectionState | 当前连接状态 | | isConnected | boolean | 是否已连接到服务 | | isAuthenticated | boolean | 是否已认证 | | scanState | ScanState | 当前扫描状态 | | isScanning | boolean | 是否正在扫描 | | authToken | string \| null | 当前认证令牌 |

事件

| 事件 | 回调 | 描述 | |------|------|------| | connect | () => void | 连接已建立 | | disconnect | (event: CloseEvent) => void | 连接已关闭 | | error | (error: Error) => void | 连接错误 | | authChange | (authenticated: boolean) => void | 认证状态变化 | | image | (image: ScannedImage) => void | 收到扫描图像 | | progress | (page: number, total?: number) => void | 扫描进度 | | scanComplete | (totalPages: number) => void | 扫描完成 | | scanCancel | () => void | 扫描已取消 | | scannersUpdate | (scanners: ScannerInfo[]) => void | 扫描仪列表已更新 |

扫描设置

interface ScanSettings {
  dpi?: number;               // 分辨率 (默认: 200)
  pixelType?: PixelType;      // 色彩模式 (默认: 'RGB')
  paperSize?: PaperSize;      // 纸张大小 (默认: 'A4')
  duplex?: boolean;           // 双面扫描 (默认: false)
  showUI?: boolean;           // 显示扫描仪原生界面 (默认: false)
  source?: string;            // 扫描仪源名称
  useAdf?: boolean;           // 使用自动进纸器 (默认: true)
  maxPages?: number;          // 最大页数, -1 = 无限制 (默认: -1)
  continuousScan?: boolean;   // 平板批量扫描模式 (默认: false)
}

type PixelType = 'RGB' | 'Gray8' | 'BlackWhite';
type PaperSize = 'A4' | 'A3' | 'A5' | 'Letter' | 'Legal' | 'B4' | 'B5' | 'USExecutive' | 'Custom';

React Hooks

| Hook | 描述 | |------|------| | useScanClient(options) | 管理连接和认证 | | useScanners(client) | 管理扫描仪列表和选择 | | useScan(client) | 管理扫描操作 | | useScannersAndScan(client) | 扫描仪和扫描的组合 Hook |

Vue Composables

| Composable | 描述 | |------------|------| | useScanClient(options) | 管理连接和认证 | | useScanners(client, isAuthenticated?) | 管理扫描仪列表和选择 | | useScan(client) | 管理扫描操作 | | useFullScanner(options) | 一体化 Composable |

连接模式

SDK 会自动选择适当的 WebSocket 协议:

| 页面协议 | WebSocket | 端口 | 说明 | |----------|-----------|------|------| | HTTP (http://) | WS | 8180 | 无加密 | | HTTPS (https://) | WSS | 8181 | 需要可信证书 |

WSS 证书设置

对于 HTTPS 页面,浏览器必须信任本地证书。有以下几种方式:

  1. 自动安装 (推荐): ScanWithWeb 服务首次运行时自动将证书安装到 Windows 受信任存储
  2. 手动接受: 访问 https://localhost:8181 并接受证书警告
  3. 导入证书: 将 .pfx 文件导入到系统的受信任根证书存储

错误处理

import { ScanError } from 'scanwith-web-sdk';

try {
  await client.scan();
} catch (error) {
  if (error instanceof ScanError) {
    switch (error.code) {
      case 'SCANNER_BUSY':
        console.error('扫描仪正忙');
        break;
      case 'SCANNER_NOT_FOUND':
        console.error('未选择扫描仪');
        break;
      case 'CONNECTION_FAILED':
        console.error('连接失败');
        break;
      case 'REQUEST_TIMEOUT':
        console.error('请求超时');
        break;
      default:
        console.error(`错误 [${error.code}]: ${error.message}`);
    }
  }
}

错误代码

| 代码 | 描述 | |------|------| | UNAUTHORIZED | 未认证 | | INVALID_TOKEN | 无效的认证令牌 | | TOKEN_EXPIRED | 认证令牌已过期 | | INVALID_REQUEST | 无效的请求格式 | | SCANNER_NOT_FOUND | 未找到扫描仪 | | SCANNER_BUSY | 扫描仪正忙 | | SCAN_FAILED | 扫描操作失败 | | NO_SCANNERS_AVAILABLE | 未检测到扫描仪 | | INTERNAL_ERROR | 内部服务器错误 | | CONNECTION_FAILED | WebSocket 连接失败 | | REQUEST_TIMEOUT | 请求超时 |

示例

多页文档扫描

const client = new ScanClient({ secure: true });
const images: ScannedImage[] = [];

client.on('image', (image) => {
  images.push(image);
  console.log(`第 ${image.pageNumber} 页已扫描`);
});

client.on('scanComplete', (totalPages) => {
  console.log(`完成: ${totalPages} 页`);
  uploadDocument(images);
});

await client.connect();
await client.authenticate();
await client.selectScanner('我的扫描仪');

await client.scan({
  dpi: 300,
  useAdf: true,
  maxPages: -1,
});

根据文档类型自定义 DPI

const scanDocument = async (type: 'photo' | 'document' | 'text') => {
  const settings: ScanSettings = { paperSize: 'A4' };

  switch (type) {
    case 'photo':
      settings.dpi = 600;
      settings.pixelType = 'RGB';
      break;
    case 'document':
      settings.dpi = 300;
      settings.pixelType = 'RGB';
      break;
    case 'text':
      settings.dpi = 200;
      settings.pixelType = 'BlackWhite';
      break;
  }

  await client.scan(settings);
};

浏览器支持

| 浏览器 | 版本 | |--------|------| | Chrome | 60+ | | Firefox | 55+ | | Safari | 11+ | | Edge | 79+ |

许可证

MIT 许可证 - 详见 LICENSE 文件。

相关项目